diff --git a/CHANGELOG.md b/CHANGELOG.md index df7bb3e..ef373f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ and this project adheres to ## [Unreleased] +- ✨(front) global layout UI + ### Changed - ♻️(chat) rewrite backend using Pydantic AI SDK #4 diff --git a/docs/assets/logo.png b/docs/assets/logo.png deleted file mode 100644 index 949969f..0000000 Binary files a/docs/assets/logo.png and /dev/null differ diff --git a/src/frontend/apps/conversations/cunningham.ts b/src/frontend/apps/conversations/cunningham.ts index 8122d62..b69a4a5 100644 --- a/src/frontend/apps/conversations/cunningham.ts +++ b/src/frontend/apps/conversations/cunningham.ts @@ -62,9 +62,9 @@ tokens.themes.default.components = { beta: false, 'image-system-filter': '', favicon: { - ico: '/assets/favicon-light.ico', - 'png-light': '/assets/favicon-light.png', - 'png-dark': '/assets/favicon-dark.png', + ico: '/assets/favicon.ico', + 'png-light': '/assets/icon-assistant.svg', + 'png-dark': '/assets/icon-assistant.svg', }, }, }; @@ -88,9 +88,9 @@ const dsfrTheme = { alpha: true, beta: true, favicon: { - ico: '/assets/favicon-dsfr.ico', - 'png-light': '/assets/favicon-dsfr.png', - 'png-dark': '/assets/favicon-dark-dsfr.png', + ico: '/assets/favicon.ico', + 'png-light': '/assets/icon-assistant.svg', + 'png-dark': '/assets/icon-assistant.svg', }, }, }, @@ -137,6 +137,7 @@ const genericTheme = { 'greyscale-350': '#eee', 'greyscale-400': '#96948E', 'greyscale-500': '#817E77', + 'greyscale-550': '#626A80', 'greyscale-600': '#6A6862', 'greyscale-700': '#3C3B38', 'greyscale-750': '#383632', diff --git a/src/frontend/apps/conversations/package.json b/src/frontend/apps/conversations/package.json index 740da02..aaa4890 100644 --- a/src/frontend/apps/conversations/package.json +++ b/src/frontend/apps/conversations/package.json @@ -41,9 +41,10 @@ "posthog-js": "1.249.3", "react": "*", "react-aria-components": "1.9.0", - "react-dom": "*", + "react-dom": "18.3.1", "react-i18next": "15.5.2", "react-intersection-observer": "9.16.0", + "react-lottie": "^1.2.10", "react-markdown": "10.1.0", "react-select": "5.10.1", "rehype-katex": "7.0.1", diff --git a/src/frontend/apps/conversations/public/assets/favicon-dark-dsfr.ico b/src/frontend/apps/conversations/public/assets/favicon-dark-dsfr.ico new file mode 100644 index 0000000..11b557f Binary files /dev/null and b/src/frontend/apps/conversations/public/assets/favicon-dark-dsfr.ico differ diff --git a/src/frontend/apps/conversations/public/assets/favicon-dark-dsfr.png b/src/frontend/apps/conversations/public/assets/favicon-dark-dsfr.png deleted file mode 100644 index 73603e6..0000000 Binary files a/src/frontend/apps/conversations/public/assets/favicon-dark-dsfr.png and /dev/null differ diff --git a/src/frontend/apps/conversations/public/assets/favicon-dsfr.ico b/src/frontend/apps/conversations/public/assets/favicon-dsfr.ico index 9763027..11b557f 100644 Binary files a/src/frontend/apps/conversations/public/assets/favicon-dsfr.ico and b/src/frontend/apps/conversations/public/assets/favicon-dsfr.ico differ diff --git a/src/frontend/apps/conversations/public/assets/favicon-dsfr.png b/src/frontend/apps/conversations/public/assets/favicon-dsfr.png index 4c433bb..58e1fea 100644 Binary files a/src/frontend/apps/conversations/public/assets/favicon-dsfr.png and b/src/frontend/apps/conversations/public/assets/favicon-dsfr.png differ diff --git a/src/frontend/apps/conversations/public/assets/favicon.ico b/src/frontend/apps/conversations/public/assets/favicon.ico new file mode 100644 index 0000000..11b557f Binary files /dev/null and b/src/frontend/apps/conversations/public/assets/favicon.ico differ diff --git a/src/frontend/apps/conversations/public/assets/icon-assistant.svg b/src/frontend/apps/conversations/public/assets/icon-assistant.svg new file mode 100644 index 0000000..df2bc17 --- /dev/null +++ b/src/frontend/apps/conversations/public/assets/icon-assistant.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/frontend/apps/conversations/src/assets/icons/conversation.svg b/src/frontend/apps/conversations/src/assets/icons/conversation.svg new file mode 100644 index 0000000..c011fbd --- /dev/null +++ b/src/frontend/apps/conversations/src/assets/icons/conversation.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/frontend/apps/conversations/src/assets/icons/left-panel-bold.svg b/src/frontend/apps/conversations/src/assets/icons/left-panel-bold.svg new file mode 100644 index 0000000..fab0e9a --- /dev/null +++ b/src/frontend/apps/conversations/src/assets/icons/left-panel-bold.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/frontend/apps/conversations/src/assets/icons/new-message-bold.svg b/src/frontend/apps/conversations/src/assets/icons/new-message-bold.svg new file mode 100644 index 0000000..358911f --- /dev/null +++ b/src/frontend/apps/conversations/src/assets/icons/new-message-bold.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/frontend/apps/conversations/src/assets/icons/search.svg b/src/frontend/apps/conversations/src/assets/icons/search.svg new file mode 100644 index 0000000..37100c1 --- /dev/null +++ b/src/frontend/apps/conversations/src/assets/icons/search.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/frontend/apps/conversations/src/assets/logo/assistant.svg b/src/frontend/apps/conversations/src/assets/logo/assistant.svg new file mode 100644 index 0000000..7d3e00c --- /dev/null +++ b/src/frontend/apps/conversations/src/assets/logo/assistant.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/frontend/apps/conversations/src/assets/logo/logo-assistant.svg b/src/frontend/apps/conversations/src/assets/logo/logo-assistant.svg new file mode 100644 index 0000000..156042d --- /dev/null +++ b/src/frontend/apps/conversations/src/assets/logo/logo-assistant.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/frontend/apps/conversations/src/assets/lotties/searching.json b/src/frontend/apps/conversations/src/assets/lotties/searching.json new file mode 100644 index 0000000..360437c --- /dev/null +++ b/src/frontend/apps/conversations/src/assets/lotties/searching.json @@ -0,0 +1,1367 @@ +{ + "v": "5.7.5", + "fr": 100, + "ip": 0, + "op": 250, + "w": 32, + "h": 32, + "nm": "Comp 1", + "ddd": 0, + "metadata": {}, + "assets": [], + "layers": [ + { + "ddd": 0, + "ind": 1, + "ty": 4, + "nm": "Group Layer 1", + "sr": 1, + "ks": { + "p": { "a": 0, "k": [0, 0], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 2 }, + "s": { "a": 0, "k": [100, 100], "ix": 2 }, + "r": { "a": 0, "k": 0, "ix": 2 }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "sk": { "a": 0, "k": 0, "ix": 2 }, + "sa": { "a": 0, "k": 0, "ix": 2 } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ty": "gr", + "nm": "1", + "it": [ + { + "ty": "sh", + "d": 1, + "ks": { + "a": 0, + "k": { + "c": true, + "v": [ + [7.5696643769856236, 0.7325899839290512], + [6.379375457763672, 0.12706007063388824], + [4.651748180389404, 0.1419292837381363], + [3.472057399962925, 0.7678571594560221], + [3.4720572850948854, 0.7678572284381218], + [1.9788318406845289, 1.6645890038040099], + [1.9788316953874576, 1.6645890910597623], + [0.9294288754463196, 2.3695242404937744], + [0.10624753621478944, 3.795316428575902], + [0.10624752256015965, 3.795316486920924], + [0.02045768908367698, 5.056593396939015], + [0.020457688354367246, 5.056593439305042], + [0.008093957959409275, 5.774809350619056], + [0.008093957619075637, 5.774809370389235], + [0.11140251321818856, 7.356708037065528], + [0.11140252487633183, 7.356708062482812], + [1.442761778831482, 8.124521255493164], + [2.863709795376492, 7.421680052580372], + [2.863710057833977, 7.421679894624164], + [3.3616746993256825, 7.1219870614174825], + [3.361675348739547, 7.121986670577238], + [4.541968755738779, 6.494355700530415], + [4.541968889219717, 6.494355656586283], + [6.2710909641044355, 6.478646748960369], + [6.271091004584529, 6.478646761476644], + [7.4625896051325205, 7.0847294456437275], + [7.462593691074168, 7.084731804658703], + [7.96685792982559, 7.375867781219133], + [7.9668597978345845, 7.375868859712513], + [9.333836555480957, 8.021649360656738], + [10.642474174499512, 7.28125524520874], + [10.79297637256701, 5.77692976968749], + [10.792976386222286, 5.776928987331546], + [10.806478485721298, 5.00336206416953], + [10.806478515010623, 5.003360386087042], + [10.758996014162642, 3.6684129445498637], + [10.758996005490678, 3.6684129030758688], + [9.908054389876359, 2.164380347231213], + [9.908054313736917, 2.164380276700672], + [8.78823438672995, 1.436132436966114], + [8.788233127308288, 1.4361317098383546], + [7.569665533292567, 0.7325906515235066], + [7.5696643769856236, 0.7325899839290512] + ], + "i": [ + [0, 0], + [0.3048640686339379, 0.09432975103317623], + [0.5603949427604675, -0.1840222030878067], + [0.584329984045993, -0.3509097167388746], + [3.8289086473816785e-8, -2.2993875853995545e-8], + [0.0003801932911429162, -0.00022831877545703705], + [4.843228765644051e-8, -2.908521113783164e-8], + [0.22135945183545336, -0.20766696913017757], + [0.12779118458014244, -0.5460403446549313], + [4.551542783788279e-9, -1.9448340822996815e-8], + [0.011083148743410456, -0.6438269262116556], + [2.4309546111466574e-10, -1.412154928672181e-8], + [0.0000016745176167048265, -0.00009727365104872376], + [1.1344440837518068e-10, -6.590055349204914e-9], + [-0.12581802250966395, -0.27431061835564563], + [-3.886047605840184e-9, -8.472427808595653e-9], + [-0.5619199229572833, 0.0532684061003188], + [-0.7470040042538475, 0.44957334498437973], + [-8.748581050829541e-8, 5.2652072520744815e-8], + [-0.0004638186234049435, 0.0002791425449926166], + [-2.164713082884484e-7, 1.302800542656117e-7], + [-0.3033946250111992, 0.09988252842076406], + [-4.4493646260176924e-8, 1.464804385707701e-8], + [-0.5640286521657067, -0.17439526478808443], + [-1.349336505570619e-8, -4.172091697694923e-9], + [-0.5908928472166375, -0.34115214233977387], + [-0.0000013619810474285824, -7.863371062797581e-7], + [-0.0007904959008531165, -0.00045639126945040687], + [-6.226693994124281e-7, -3.59498315197482e-7], + [-0.2929328598437415, -0.025679737339612707], + [-0.2408164143562317, 0.4939669668674469], + [-0.01442851322951988, 0.8266712799517713], + [-4.5518353933576856e-9, 2.6078531716389135e-7], + [-0.000016214360709199127, 0.000928958724242257], + [-9.76294258843616e-9, 5.593607867191688e-7], + [0.06532925207302398, 0.3124394568472777], + [2.8906560591596894e-9, 1.3824664813592088e-8], + [0.4328092972606097, 0.4009259250729409], + [2.5379814507282867e-8, 2.351017892912394e-8], + [0.5904351127146548, 0.34088782213198354], + [4.198071682114078e-7, 2.4237603302637977e-7], + [0.0009681610523353257, 0.0005589686240834446], + [3.8543570825142673e-7, 2.225313993389674e-7] + ], + "o": [ + [-0.5902829488197741, -0.3408000032034141], + [-0.5634793043136597, -0.17434938251972198], + [-0.303195406993674, 0.09956314833596555], + [-3.8289085433128296e-8, 2.299387758693565e-8], + [-0.00033804995403130533, 0.00020301029330538345], + [-4.843228943930219e-8, 2.9085208169035035e-8], + [-0.5520289652014332, 0.33151188564489154], + [-0.4089891837718058, 0.3836906185961917], + [-4.551543689283257e-9, 1.944834061108159e-8], + [-0.0691651108174881, 0.2955364174604722], + [-2.430951936168686e-10, 1.412154929132665e-8], + [-0.000002451269965106362, 0.00014239562297291286], + [-1.1344453153327111e-10, 6.59005534708481e-9], + [-0.015006307167333041, 0.8717255987870338], + [3.886047578781216e-9, 8.472427821006786e-9], + [0.2353189021460368, 0.5130463166313239], + [0.3004418733617569, -0.028481032894205782], + [8.7485822654369e-8, -5.265205233902509e-8], + [0.00029489412612049293, -0.00017747777410104248], + [2.164712735329126e-7, -1.302801120148511e-7], + [0.5845990567823791, -0.35183229335754884], + [4.449364614860976e-8, -1.4648044195963879e-8], + [0.5607671482687628, -0.1846138150471326], + [1.3493365198403083e-8, 4.1720912361850814e-9], + [0.3051589118991842, 0.0943538515082123], + [0.000001361979742620627, 7.863393662777748e-7], + [0.0011688186032499104, 0.0006748151451745343], + [6.226699838215097e-7, 3.5949730296927544e-7], + [0.7160286637159663, 0.4133993070510549], + [0.5474420189857483, 0.04799109324812889], + [0.1288594102262568, -0.26431875232050217], + [4.551681579791964e-9, -2.6078531984856016e-7], + [0.000011072625616392884, -0.0006343766708747367], + [9.763272826125567e-9, -5.593607809551856e-7], + [0.01189774110412678, -0.6816725350887651], + [-2.8906560695125967e-9, -1.3824664811427357e-8], + [-0.12074807955615441, -0.5774819600067018], + [-2.5379812677277522e-8, -2.351018090465941e-8], + [-0.2341661454112547, -0.21691600528372978], + [-4.198072922201775e-7, -2.42375818236986e-7], + [-0.0010103959342728785, -0.0005833529698371365], + [-3.8543560370507147e-7, -2.225315804184768e-7], + [0, 0] + ] + } + } + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 0.4666666666666667, 0.4666666666666667, 0.4666666666666667 + ], + "ix": 2 + }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "r": 1, + "bm": 0 + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [-7.9960222244262695, -6.4562296867370605], + "ix": 2 + }, + "a": { + "a": 0, + "k": [5.4069929122924805, 4.065237522125244], + "ix": 2 + }, + "s": { + "a": 1, + "k": [ + { + "t": 0, + "s": [100, 100], + "i": { "x": [0], "y": [0.75] }, + "o": { "x": [0.5], "y": [0.25] } + }, + { + "t": 125, + "s": [70, 70], + "i": { "x": [0], "y": [0.75] }, + "o": { "x": [0.5], "y": [0.25] } + }, + { + "t": 250, + "s": [100, 100], + "i": { "x": [0.75], "y": [0.75] }, + "o": { "x": [0.25], "y": [0.25] } + } + ], + "ix": 2 + }, + "r": { "a": 0, "k": 0, "ix": 2 }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "sk": { "a": 0, "k": 0, "ix": 2 }, + "sa": { "a": 0, "k": 0, "ix": 2 } + } + ] + }, + { + "ty": "gr", + "nm": "2", + "it": [ + { + "ty": "sh", + "d": 1, + "ks": { + "a": 0, + "k": { + "c": true, + "v": [ + [9.7105712890625, 5.053908348083496], + [9.63983154296875, 3.720322608947754], + [8.763139777094667, 2.2315881720813633], + [8.763139672368222, 2.2315880783764044], + [7.631225871153229, 1.5229101570810537], + [7.631225300720743, 1.5229098407057657], + [6.108021081780309, 0.6781046858482551], + [6.108020483161856, 0.6781043538403704], + [4.972827911376953, 0.12176228314638138], + [3.3264646530151367, 0.12176233530044556], + [2.1912719391260875, 0.6781044326546318], + [2.191271624716628, 0.6781046070335364], + [1.5630962538274094, 1.0265055405837962], + [1.5630960774866711, 1.026505638386527], + [0.2447863518468167, 1.9069228945442587], + [0.24478629238688598, 1.9069229783347688], + [0.24552160501480103, 3.4438202381134033], + [1.564673292861684, 4.322976450795975], + [1.564673793091021, 4.322976727609897], + [2.0731968501042255, 4.604380322781617], + [2.0731978795099297, 4.604380892428009], + [3.206889125162578, 5.3127293342258515], + [3.2068891798911316, 5.312729383120343], + [4.085053433398461, 6.8023385539671954], + [4.085053454418921, 6.802338646350185], + [4.155920028686523, 8.137249946594238], + [4.155920028686523, 8.719528198242188], + [4.280147546583525, 10.226254450523053], + [4.280147558396944, 10.226254475868544], + [5.575665473937988, 10.989372253417969], + [6.953702806197483, 10.367547101977507], + [6.953703047074649, 10.367546968456736], + [7.630382235071983, 9.992456603985707], + [7.630382840611561, 9.992456268328677], + [8.762741088867188, 9.28386116027832], + [9.639799113344784, 7.794907590543425], + [9.6397991227392, 7.79490754922708], + [9.7105712890625, 6.460991382598877], + [9.7105712890625, 5.053908348083496] + ], + "i": [ + [0, 0], + [0.07074009627103806, 0.31118500232696533], + [0.43956530271207406, 0.39330517692237565], + [3.4908816821740556e-8, 3.123498446407122e-8], + [0.5960615322570506, 0.3305896713519967], + [1.901437986807745e-7, 1.054582565437734e-7], + [0.0007791391115778978, 0.0004321290143680656], + [1.99539236039424e-7, 1.1066912597578436e-7], + [0.29052455046253556, 0.08786941998048237], + [0.5367804169654846, -0.16234976053237915], + [0.5631119156894666, -0.3123151078702673], + [1.0480312817229021e-7, -5.812629623241152e-8], + [0.0002715745955357285, -0.00015062170056001634], + [5.878024870891494e-8, -3.2600905974178086e-8], + [0.17465097392083526, -0.2461168939173941], + [1.9819976657883685e-8, -2.7930170072948337e-8], + [-0.3270917435594809, -0.46000273620270704], + [-0.7628438593455115, -0.42213776421214044], + [-1.6674309414559752e-7, -9.227134446631359e-8], + [-0.0005899384129950391, -0.0003264567615124392], + [-3.431352143431787e-7, -1.8988202659270262e-7], + [-0.23819808816733268, -0.21280616677025233], + [-1.8242850471338308e-8, -1.629816415348915e-8], + [-0.1309835942739226, -0.5756608541120487], + [-7.006819739423173e-9, -3.079432948033118e-8], + [0, -0.6823058983140884], + [0, 0], + [-0.12422715281856433, -0.2665272657416633], + [-3.937805839386941e-9, -8.448495968988282e-9], + [-0.5481961323022935, 0.038430325361010986], + [-0.7231327684476367, 0.40084026773579495], + [-8.02921823760589e-8, 4.450679803039805e-8], + [-0.0005221938177246699, 0.00028945750495878997], + [-2.0184651401701886e-7, 1.1188570204964401e-7], + [-0.23791595246940567, 0.2127965775580858], + [-0.1308073874891189, 0.5752867974286273], + [-3.131471714418751e-9, 1.3772114382415036e-8], + [0, 0.6817762104834229], + [0, 0] + ], + "o": [ + [0, -0.6816004514694214], + [-0.1307486541805183, -0.5751620898741305], + [-3.4908813458453e-8, -3.123498822294541e-8], + [-0.2378218602557567, -0.21279329368959976], + [-1.9014382292403867e-7, -1.0545821283256724e-7], + [-0.0007605783576583036, -0.00042183478041391], + [-1.995392094159631e-7, -1.1066917397854477e-7], + [-0.5631120593718686, -0.31231511493206265], + [-0.5367804169654846, -0.16234973073005676], + [-0.2905245749009646, 0.08786944227306764], + [-1.0480313551106047e-7, 5.8126283000428e-8], + [-0.00036260990041991903, 0.00020111203602590777], + [-5.8780243764430336e-8, 3.260091488920588e-8], + [-0.7624395661361633, 0.4228668839032721], + [-1.9819977502251244e-8, 2.7930169473763094e-8], + [-0.326651722247204, 0.4603152826296044], + [0.17488622244181723, 0.24594977790290162], + [1.667431338859443e-7, 9.227127265169128e-8], + [0.00041129072043766257, 0.000227597718128969], + [3.4313513577337747e-7, 1.8988216857587455e-7], + [0.5969951069120789, 0.33036143196022927], + [1.8242851435470748e-8, 1.6298163074316994e-8], + [0.4402638409557089, 0.39333175273323295], + [7.006819942215771e-9, 3.079432943418856e-8], + [0.07086670092745972, 0.3114526445237109], + [0, 0], + [0, 0.8267993199914283], + [3.9378057667460055e-9, 8.448496002845895e-9], + [0.23215948328382652, 0.49809425316692924], + [0.2933364943731327, -0.020563837861804046], + [8.029217310772026e-8, -4.450681475087642e-8], + [0.00032938222399625516, -0.00018258001818338698], + [2.0184654127616516e-7, -1.1188565287298303e-7], + [0.5962948568807507, -0.3305326773444772], + [0.4397399968917117, -0.39331185346932784], + [3.1314716627497565e-9, -1.3772114394163415e-8], + [0.07077174909080419, -0.311251932768311], + [0, 0], + [0, 0] + ] + } + } + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 0.4666666666666667, 0.4666666666666667, 0.4666666666666667 + ], + "ix": 2 + }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "r": 1, + "bm": 0 + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [0.7052001953125, -9.503662109375], + "ix": 2 + }, + "a": { + "a": 0, + "k": [4.85528564453125, 5.49631929397583], + "ix": 2 + }, + "s": { + "a": 1, + "k": [ + { + "t": 0, + "s": [100, 100], + "i": { "x": [0], "y": [0.75] }, + "o": { "x": [0.5], "y": [0.25] } + }, + { + "t": 125, + "s": [70, 70], + "i": { "x": [0], "y": [0.75] }, + "o": { "x": [0.5], "y": [0.25] } + }, + { + "t": 250, + "s": [100, 100], + "i": { "x": [0.75], "y": [0.75] }, + "o": { "x": [0.25], "y": [0.25] } + } + ], + "ix": 2 + }, + "r": { "a": 0, "k": 0, "ix": 2 }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "sk": { "a": 0, "k": 0, "ix": 2 }, + "sa": { "a": 0, "k": 0, "ix": 2 } + } + ] + }, + { + "ty": "gr", + "nm": "3", + "it": [ + { + "ty": "sh", + "d": 1, + "ks": { + "a": 0, + "k": { + "c": true, + "v": [ + [7.434703544845893, 9.776938050657986], + [8.55425352434481, 9.048882534305946], + [8.554253632027255, 9.048882434566119], + [9.405190461696924, 7.545278578519721], + [9.405190473972022, 7.545278519868951], + [9.452965763887665, 6.210674929619843], + [9.452965708888835, 6.210671734807375], + [9.422984150730306, 4.469137835505123], + [9.422984095731476, 4.469134640692665], + [9.337197310599285, 3.2078593068977304], + [9.337197296944655, 3.2078592485527087], + [8.51401424407959, 1.7820669412612915], + [7.464613485540996, 1.0771332320745162], + [7.464610528742125, 1.0771314564154024], + [6.848802308889687, 0.7073169954284098], + [6.848800916817961, 0.7073161594416129], + [5.427182197570801, 0.005834728945046663], + [4.0965566706374235, 0.7749196731299666], + [4.096556656388944, 0.7749197042732924], + [3.994759316446954, 2.3569168805915925], + [3.9947593259786864, 2.356917405144495], + [4.005318631413707, 2.93801370486148], + [4.005318651911259, 2.9380148328884403], + [3.9587161540985107, 4.273993492126465], + [3.1077582991548445, 5.779311165715419], + [3.10775826807538, 5.7793111945140705], + [1.9871246308514325, 6.508139433040263], + [1.9871240168384237, 6.508139787540872], + [1.4828577371547724, 6.799278171403754], + [1.482857432751836, 6.799278347150884], + [0.24010929465293884, 7.660224914550781], + [0.2269886355873269, 9.163736331793817], + [0.22698865122831663, 9.16373635497376], + [1.4545216115080686, 10.046237703917482], + [1.454522415820608, 10.046238187195524], + [2.117700442413639, 10.444714035700008], + [2.117702141661832, 10.444715056707752], + [3.297541379928589, 11.07106876373291], + [5.025542238977202, 11.086146360936477], + [5.025542279455412, 11.08614634841411], + [6.216133394989488, 10.480480584588685], + [6.2161347477105275, 10.480479803594516], + [7.434702201614244, 9.776938826173456], + [7.434703544845893, 9.776938050657986] + ], + "i": [ + [0, 0], + [-0.23412401176253006, 0.2168550921546153], + [-3.589414653790292e-8, 3.3246612409241045e-8], + [-0.12082963328975893, 0.5773272755634995], + [-4.091699338612162e-9, 1.9550255727291663e-8], + [0.011731640363878526, 0.6814983949362192], + [1.8333535301909023e-8, 0.000001064937087422233], + [0.00003310739260697915, 0.001923103737083487], + [1.8332338564340347e-8, 0.0000010649371080241204], + [0.06916505437372145, 0.29553617628206164], + [4.551543689285173e-9, 1.944834061108114e-8], + [0.4089891837718067, 0.38369061859619125], + [0.5520279088236819, 0.3315112334373205], + [9.855993248437545e-7, 5.918870052656856e-7], + [0.0007545274953875136, 0.0004531202572671811], + [4.6402405758626406e-7, 2.786619687951528e-7], + [0.3004688879969386, 0.028193737010441623], + [0.23482823739148362, -0.5132711436419526], + [4.749492481976526e-9, -1.0381108726355394e-8], + [-0.015839822838556117, -0.8717110031767867], + [-3.177278499497829e-9, -1.7485096824677442e-7], + [-0.000011996243176604082, -0.0006601733952171784], + [-6.832440713196022e-9, -3.760088932685341e-7], + [0.06519654086573734, -0.31268871153906685], + [0.43304514042473485, -0.40126550016283336], + [1.0359821054550519e-8, -9.599549852895163e-9], + [0.5908940641292206, -0.3411527807086898], + [2.0467099049912013e-7, -1.1816689673338676e-7], + [0.000319582885091485, -0.00018451133542818354], + [1.0146764931715988e-7, -5.8582361284362514e-8], + [0.16870580908374266, -0.24084748111337717], + [-0.3073796550815735, -0.4555366620831691], + [-5.213663912127153e-9, -7.726649181767226e-9], + [-0.7087039538330354, -0.4258311500596328], + [-2.6810424147464725e-7, -1.6109258875506498e-7], + [-0.0008654252761552961, -0.0005199977342513762], + [-5.664159501001686e-7, -3.4033611460098266e-7], + [-0.3032451292553171, -0.09964297353341856], + [-0.5636166872377704, 0.17436083512537515], + [-1.3492737073247987e-8, 4.174122175107857e-9], + [-0.5904351005486745, 0.3408877853045009], + [-4.509069539309432e-7, 2.603315213105346e-7], + [-0.0010434160509431578, 0.0006024171628613573], + [-4.4774396502992524e-7, 2.585050412414695e-7] + ], + "o": [ + [0.5902828412636341, -0.3407998815010025], + [3.58941504122373e-8, -3.324660822638053e-8], + [0.4327306680446821, -0.4008126724194856], + [4.0917003320538434e-9, -1.9550255519372888e-8], + [0.0653734599905551, -0.31235624976949605], + [-1.8332338350848505e-8, -0.0000010649371080277955], + [-0.000033107392608755504, -0.0019231037372939852], + [-1.8333535302931786e-8, -0.0000010649370874222151], + [-0.011083129717896156, -0.6438259946055727], + [-4.551542783776226e-9, -1.9448340822999637e-8], + [-0.12779118458020022, -0.5460403446549358], + [-0.22135926393675476, -0.20766679285420198], + [-9.856000427574432e-7, -5.918858098074096e-7], + [-0.001099445600896587, -0.0006602556919064195], + [-4.6402371657382186e-7, -2.7866253664356946e-7], + [-0.747432900367567, -0.44885845928360546], + [-0.5619706454667117, -0.05273108876070336], + [-4.7494925245673e-9, 1.038110870686956e-8], + [-0.12555565918609535, 0.2744307847978019], + [3.1772093847373103e-9, 1.748509695026694e-7], + [0.000008181492483938513, 0.0004502412623716978], + [6.832589923145262e-9, 3.760088905572172e-7], + [0.012396111771141527, 0.6821937377221188], + [-0.12050339435440938, 0.5779455831060751], + [-1.0359820636651111e-8, 9.599550303891616e-9], + [-0.2342925535262279, 0.21709863866318635], + [-2.0467102004391017e-7, 1.181668455603141e-7], + [-0.00045384677308724086, 0.0002620286570040875], + [-1.0146763381195607e-7, 5.8582388140161234e-8], + [-0.7160291849167283, 0.41339954836073556], + [-0.31528255076051737, 0.45010310029728906], + [5.213663466651593e-9, 7.726649482358034e-9], + [0.1644770077041739, 0.24375490219861007], + [2.6810412754130404e-7, 1.6109277837265285e-7], + [0.0005954875297229378, 0.0003578034693028087], + [5.664161874441085e-7, 3.40335719592949e-7], + [0.5843967495620364, 0.35114003957023776], + [0.560488038506687, 0.18417013961166262], + [1.3492737077376444e-8, -4.17412216176273e-9], + [0.3049378649048462, -0.0943357820595967], + [4.5090709698174364e-7, -2.6033127353938017e-7], + [0.0010470941651261612, -0.0006045407252788237], + [4.4774382400600563e-7, -2.5850528550196395e-7], + [0, 0] + ] + } + } + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 0.4666666666666667, 0.4666666666666667, 0.4666666666666667 + ], + "ix": 2 + }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "r": 1, + "bm": 0 + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [8.689452171325684, -4.327481269836426], + "ix": 2 + }, + "a": { + "a": 0, + "k": [4.730164527893066, 5.6065826416015625], + "ix": 2 + }, + "s": { + "a": 1, + "k": [ + { + "t": 0, + "s": [100, 100], + "i": { "x": [0], "y": [0.75] }, + "o": { "x": [0.5], "y": [0.25] } + }, + { + "t": 125, + "s": [70, 70], + "i": { "x": [0], "y": [0.75] }, + "o": { "x": [0.5], "y": [0.25] } + }, + { + "t": 250, + "s": [100, 100], + "i": { "x": [0.75], "y": [0.75] }, + "o": { "x": [0.25], "y": [0.25] } + } + ], + "ix": 2 + }, + "r": { "a": 0, "k": 0, "ix": 2 }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "sk": { "a": 0, "k": 0, "ix": 2 }, + "sa": { "a": 0, "k": 0, "ix": 2 } + } + ] + }, + { + "ty": "gr", + "nm": "4", + "it": [ + { + "ty": "sh", + "d": 1, + "ks": { + "a": 0, + "k": { + "c": true, + "v": [ + [3.2443224701296676, 7.39788440326138], + [4.434610366821289, 8.003414154052734], + [6.162239054579025, 7.98854590123243], + [6.162239094835037, 7.988545888013176], + [7.341928170640404, 7.362617202853944], + [7.3419287934714195, 7.362616828822897], + [8.835153245311767, 6.465884886334989], + [8.835153914111768, 6.465884484698026], + [9.884557708548403, 5.760949626158722], + [9.88455773944964, 5.760949597168914], + [10.707738876342773, 4.3351569175720215], + [10.793527595694314, 3.073881105528457], + [10.793527610604382, 3.0738802393811944], + [10.805891033483439, 2.3556649336183715], + [10.805891040491233, 2.355664526525707], + [10.702583312988281, 0.7737652063369751], + [9.371223449707031, 0.0059543801471591], + [7.950276022269434, 0.7087948280780718], + [7.950274820015254, 0.7087955516363097], + [7.4523118380562465, 1.0084865758947053], + [7.452309286212266, 1.008488111682847], + [6.272016525268555, 1.6361175775527954], + [4.542896340865537, 1.6518284299200843], + [4.542896200638369, 1.6518283865624532], + [3.3513939460209348, 1.045744415132504], + [3.351391069084133, 1.0457427541348299], + [2.8471264315953837, 0.7546062720643233], + [2.847125105728123, 0.7546055065756979], + [1.4801499843597412, 0.10882722586393356], + [0.1715104579925537, 0.8492199182510376], + [0.021009220960375437, 2.353547080322504], + [0.02100920375883022, 2.353548065856709], + [0.007507110254699592, 3.1271118112193452], + [0.007507073453619489, 3.1271139196765585], + [0.05499053746461868, 4.462062358856201], + [0.905933141708374, 5.966094493865967], + [2.0257499950010405, 6.694342581716755], + [2.025751755945429, 6.6943435983973965], + [3.2443206992729796, 7.397883380857873], + [3.2443224701296676, 7.39788440326138] + ], + "i": [ + [0, 0], + [-0.304864015734454, -0.09432970486293435], + [-0.560394877484736, 0.1840222413060646], + [-1.3418671072874667e-8, 4.406417626431301e-9], + [-0.5843298017940279, 0.3509096668956708], + [-2.0761014649642615e-7, 1.24676936356347e-7], + [-0.0008153665025414369, 0.000489655246916243], + [-2.2293324138790472e-7, 1.338788913772574e-7], + [-0.22135939934059579, 0.2076669593280096], + [-1.0300411965841294e-8, 9.663268753091851e-9], + [-0.12779113489400729, 0.5460403492831833], + [-0.011083086638667083, 0.6438266728013198], + [-4.9699790523959435e-9, 2.8871575932717275e-7], + [-0.000007592682623069891, 0.0004410737161038014], + [-2.335951456255829e-9, 1.3569754518725002e-7], + [0.12581803415445947, 0.27431059728865126], + [0.5619199275970459, -0.05326845496892929], + [0.7470036313838433, -0.449573216054552], + [4.0075151424321257e-7, -2.4118586346347576e-7], + [0.0009184076480446279, -0.0005527288949198628], + [8.506144351008768e-7, -5.119298521006401e-7], + [0.30339445452929503, -0.09988250767338869], + [0.5640287795020296, 0.17439521125875412], + [4.6742388639843276e-8, 1.4452546371369942e-8], + [0.5908932776360394, 0.34115232662615647], + [9.589793117286176e-7, 5.536653622696924e-7], + [0.0006662405948518746, 0.00038465307415502803], + [4.419556108959953e-7, 2.5516313271665276e-7], + [0.2929328968094378, 0.025679716365802824], + [0.2408163845539093, -0.4939669966697693], + [0.014428439403983116, -0.8266712718309905], + [5.733970532008435e-9, -3.2851140596724766e-7], + [0.00001817196085036627, -0.0010411104094107948], + [1.2266766309961182e-8, -7.028190903229064e-7], + [-0.06532927960025464, -0.3124394296266466], + [-0.43280935287475586, -0.400925874710083], + [-0.5904349775348596, -0.34088771428552533], + [-5.86981604149031e-7, -3.3889334891802956e-7], + [-0.0011978482257646306, -0.0006915766931285816], + [-5.902854601264536e-7, -3.4080139350258753e-7] + ], + "o": [ + [0.5902827439694875, 0.3407998253289808], + [0.5634792975673957, 0.17434933572883793], + [1.3418671040996049e-8, -4.4064177235098576e-9], + [0.3031953558677003, -0.0995631648687052], + [2.0761017833969682e-7, -1.246768833313995e-7], + [0.0007868513521307463, -0.00047253093168642124], + [2.229332046164737e-7, -1.3387895260850653e-7], + [0.5520287778573287, -0.3315118029405335], + [1.0300412268078727e-8, -9.66326843092652e-9], + [0.40898915301537997, -0.3836906830322322], + [0.06916508017882173, -0.2955363624252332], + [4.970067089411735e-9, -2.887157578116822e-7], + [0.000011073977065123586, -0.0006433088879091997], + [2.335909761042446e-9, -1.356975459050016e-7], + [0.01500622880826974, -0.8717254831417969], + [-0.23531894385814667, -0.5130463242530823], + [-0.3004418103647257, 0.02848105299929138], + [-4.007512591831895e-7, 2.411862872676185e-7], + [-0.0006305080265667229, 0.0003794611309088092], + [-8.506149713460306e-7, 5.119289610832333e-7], + [-0.5845984059020086, 0.3518319971094701], + [-0.5607671505310448, 0.18461389074679868], + [-4.674239031551434e-8, -1.4452540951917744e-8], + [-0.3051590280815413, -0.09435386449701655], + [-9.589786648355255e-7, -5.536664827231245e-7], + [-0.0009811952343761199, -0.0005664916940897324], + [-4.4195590528639554e-7, -2.5516262281672664e-7], + [-0.7160288444283274, -0.41339935177972503], + [-0.5474420189857483, -0.04799104481935501], + [-0.12885939004661479, 0.2643187414931827], + [-5.733726497442738e-9, 3.2851141022663473e-7], + [-0.000012425590374967599, 0.0007118885842309908], + [-1.2267287612773125e-8, 7.028190812240703e-7], + [-0.011897679258045711, 0.6816724067126914], + [0.1207481324672699, 0.5774819254875183], + [0.23416613323465651, 0.21691597646599714], + [5.86981361759418e-7, 3.388937687495985e-7], + [0.0011944921663213925, 0.0006896390749551173], + [5.902857052285391e-7, 3.4080096897287547e-7], + [0, 0] + ] + } + } + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 0.4666666666666667, 0.4666666666666667, 0.4666666666666667 + ], + "ix": 2 + }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "r": 1, + "bm": 0 + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [7.996166706085205, 6.456369400024414], + "ix": 2 + }, + "a": { + "a": 0, + "k": [5.406992435455322, 4.065237045288086], + "ix": 2 + }, + "s": { + "a": 1, + "k": [ + { + "t": 0, + "s": [100, 100], + "i": { "x": [0], "y": [0.75] }, + "o": { "x": [0.5], "y": [0.25] } + }, + { + "t": 125, + "s": [70, 70], + "i": { "x": [0], "y": [0.75] }, + "o": { "x": [0.5], "y": [0.25] } + }, + { + "t": 250, + "s": [100, 100], + "i": { "x": [0.75], "y": [0.75] }, + "o": { "x": [0.25], "y": [0.25] } + } + ], + "ix": 2 + }, + "r": { "a": 0, "k": 0, "ix": 2 }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "sk": { "a": 0, "k": 0, "ix": 2 }, + "sa": { "a": 0, "k": 0, "ix": 2 } + } + ] + }, + { + "ty": "gr", + "nm": "5", + "it": [ + { + "ty": "sh", + "d": 1, + "ks": { + "a": 0, + "k": { + "c": true, + "v": [ + [1.103714090001554e-10, 5.938731317073891], + [0.0707402229309082, 7.27231502532959], + [0.9474305366039477, 8.761050158044801], + [0.9474306847095268, 8.7610502905636], + [2.0793445255461975, 9.469726863061744], + [2.0793468807067934, 9.469728169290006], + [3.6025493628706076, 10.314533552981125], + [3.60255165030929, 10.314534821649238], + [4.737743377685547, 10.870875358581543], + [6.38410758972168, 10.870875358581543], + [7.51929537344244, 10.314537008894103], + [7.519305548176876, 10.314531365718869], + [8.147472878070811, 9.966131568097394], + [8.147477607161887, 9.966128945219092], + [9.465785012535456, 9.085715313633887], + [9.46578504056513, 9.085715274134662], + [9.46505069732666, 7.5488176345825195], + [8.145898528392268, 6.669661888851934], + [8.145897202198903, 6.669661154970513], + [7.637375356137046, 6.388257819040052], + [7.637372492372943, 6.388256234306411], + [6.503683144938519, 5.6799097550102084], + [6.5036830354814015, 5.679909657221237], + [5.625516898126748, 4.190299063332718], + [5.625516884832236, 4.1902990049045865], + [5.554650306701671, 2.855386142284462], + [5.55465030670166, 2.855385895221453], + [5.55465030670166, 2.273110685776036], + [5.554650306701655, 2.2731105704800685], + [5.430423736572266, 0.7663833498954773], + [4.134904861450195, 0.0032665350008755922], + [2.7568694560643903, 0.6250887929421715], + [2.7568691761375197, 0.6250889481087253], + [2.0801890816437787, 1.0001802997071703], + [2.0801884207975876, 1.000180666021497], + [0.9478311538696289, 1.708777666091919], + [0.0707712288519492, 3.197730252208689], + [0.0707712058404366, 3.19773035341265], + [1.1034981007547518e-10, 4.5316475586585305], + [1.1036060953983906e-10, 4.531647805721355], + [1.1036060953983906e-10, 5.938731070011066], + [1.103714090001554e-10, 5.938731317073891] + ], + "i": [ + [0, 0], + [-0.07074011434919751, -0.31118498352752333], + [-0.43956538050773586, -0.39330508522301066], + [-4.9368526909727545e-8, -4.417293295927765e-8], + [-0.5960609788333469, -0.3305892717463852], + [-7.850521944142761e-7, -4.354091646291973e-7], + [-0.0015220385609562292, -0.0008441598444974829], + [-7.62478400014756e-7, -4.2288826836925847e-7], + [-0.29052443504714454, -0.08786935370752857], + [-0.5367804169654846, 0.16234980523586273], + [-0.5631086289146312, 0.31231334454365545], + [-0.000003391574295974964, 0.0000018810653501119813], + [-0.0014023499322739497, 0.0007777838950673299], + [-0.0000015763650948125628, 8.742893446075141e-7], + [-0.1746507780511255, 0.24611667334115594], + [-9.343224366099333e-9, 1.3166410035557732e-8], + [0.32709177860723493, 0.46000271377725355], + [0.7628436436290613, 0.4221375522517441], + [4.4206431903935836e-7, 2.446273941557116e-7], + [0.0009830826738799203, 0.0005440134894252324], + [9.545883911828772e-7, 5.282440271412982e-7], + [0.2381979765816924, 0.21280603109238871], + [3.6485705299816234e-8, 3.259632342994698e-8], + [0.13098364899346215, 0.5756608489268986], + [4.4315038573099475e-9, 1.9476043644507134e-8], + [5.964904392641301e-8, 0.6823058283575967], + [0, 8.235427432014552e-8], + [0, 0.00021143583600924387], + [3.3598300870147113e-15, 3.843198892837261e-8], + [0.12422718564036739, 0.2665272591158141], + [0.5481961369514465, -0.03843037411570549], + [0.723132695826084, -0.4008403201247579], + [9.330886588533856e-8, -5.172214892357645e-8], + [0.0005454942458764656, -0.00030237356712559116], + [2.2028208255171435e-7, -1.2210474774646523e-7], + [0.23791593389043886, -0.21279660407090062], + [0.13080734661316573, -0.5752867855411545], + [7.670502793661131e-9, -3.3734654060712496e-8], + [-5.960281487294892e-8, -0.681776106265993], + [0, -8.235427432014552e-8], + [0, -0.00048119361278420314], + [-7.199640366305295e-15, -8.235427432014522e-8] + ], + "o": [ + [5.958740541009176e-8, 0.6816003691149248], + [0.1307486967334807, 0.5751620804140201], + [4.936852623236337e-8, 4.417293371631298e-8], + [0.2378217678081247, 0.21279316324051045], + [7.850526068721159e-7, 4.354084209579104e-7], + [0.0015443971180144622, 0.0008565604475663235], + [7.624780109436126e-7, 4.2288896987375357e-7], + [0.563111496432736, 0.3123147431070219], + [0.5367804765701294, 0.16234970092773438], + [0.2905237270309433, -0.08786920818562294], + [0.0000033915819929478842, -0.0000018810514723477507], + [0.0020562562006629648, -0.0011404592535715352], + [0.0000015763615317673568, -8.742957688343968e-7], + [0.7624380118142131, -0.4228661443002615], + [9.343225602639585e-9, -1.3166409158076768e-8], + [0.32665164856486584, -0.4603153446042594], + [-0.17488620578348524, -0.24594971861832082], + [-4.4206459844233737e-7, -2.4462688924801477e-7], + [-0.0006691363103534798, -0.0003702833838579167], + [-9.545877831387321e-7, -5.282451259347799e-7], + [-0.596994532201526, -0.33036102134103373], + [-3.6485705609677284e-8, -3.2596323083113404e-8], + [-0.4402638604394564, -0.3933316643182403], + [-4.431503844083078e-9, -1.9476043647516724e-8], + [-0.07086673031595048, -0.3114526303074894], + [-7.199636178214856e-15, -8.235427432014522e-8], + [0, -0.00030949323422380104], + [0, -3.843198892837275e-8], + [-7.228106646550714e-8, -0.8267992946636089], + [-0.23215952515602112, -0.49809423089027405], + [-0.2933364917330392, 0.020563861891109564], + [-9.33088783827276e-8, 5.172212637777056e-8], + [-0.0003550612529372721, 0.00019681442730190568], + [-2.202820500712095e-7, 1.2210480634263894e-7], + [-0.5962948384448377, 0.33053269692841614], + [-0.43973995945986866, 0.3933119062499806], + [-7.670505588475689e-9, 3.37346534252343e-8], + [-0.07077170075490048, 0.3112519356915975], + [7.199645926553799e-15, 8.235427432014522e-8], + [0, 0.00048119361121568005], + [0, 8.235427432014552e-8], + [0, 0] + ] + } + } + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 0.4666666666666667, 0.4666666666666667, 0.4666666666666667 + ], + "ix": 2 + }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "r": 1, + "bm": 0 + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [-0.7059321403503418, 9.503662109375], + "ix": 2 + }, + "a": { + "a": 0, + "k": [4.855286121368408, 5.496318817138672], + "ix": 2 + }, + "s": { + "a": 1, + "k": [ + { + "t": 0, + "s": [100, 100], + "i": { "x": [0], "y": [0.75] }, + "o": { "x": [0.5], "y": [0.25] } + }, + { + "t": 125, + "s": [70, 70], + "i": { "x": [0], "y": [0.75] }, + "o": { "x": [0.5], "y": [0.25] } + }, + { + "t": 250, + "s": [100, 100], + "i": { "x": [0.75], "y": [0.75] }, + "o": { "x": [0.25], "y": [0.25] } + } + ], + "ix": 2 + }, + "r": { "a": 0, "k": 0, "ix": 2 }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "sk": { "a": 0, "k": 0, "ix": 2 }, + "sa": { "a": 0, "k": 0, "ix": 2 } + } + ] + }, + { + "ty": "gr", + "nm": "6", + "it": [ + { + "ty": "sh", + "d": 1, + "ks": { + "a": 0, + "k": { + "c": true, + "v": [ + [2.02562482636138, 1.4362280124154798], + [0.9060759891916119, 2.164283004994772], + [0.9060759196829019, 2.1642830693765673], + [0.05513942303129707, 3.66788695169119], + [0.05513941435149683, 3.6678869931635463], + [0.007363590411841869, 5.002492904663086], + [0.03734350949525833, 6.744029998779297], + [0.12313354015350342, 8.005306243896484], + [0.9463151097297668, 9.431097030639648], + [1.9957176924969573, 10.136033825782183], + [1.9957183121416753, 10.13603419789954], + [2.6115299227022177, 10.505849752480051], + [2.61153020837069, 10.505849924033516], + [4.033149242401123, 11.207330703735352], + [5.363773815660187, 10.438245788887096], + [5.363773829908661, 10.438245757743763], + [5.4655694987013375, 8.856248995733685], + [5.465569493608262, 8.856248715447874], + [5.455010419525799, 8.27515059637532], + [5.4550104087212095, 8.27515000176922], + [5.501612186431885, 6.939170837402344], + [6.352569103240967, 5.4338531494140625], + [7.473201046058065, 4.705025952552423], + [7.473205318385927, 4.705023485927307], + [7.977469389411359, 4.413887606203151], + [7.97747140648596, 4.413886441646697], + [9.220220565795898, 3.55293869972229], + [9.2333402633667, 2.0494282245635986], + [8.005806380924751, 1.1669257968725686], + [8.005805557551616, 1.1669253021420023], + [7.342628838293346, 0.768450404927664], + [7.3426271660406, 0.7684494001407953], + [6.162786960601807, 0.1420966535806656], + [4.434785871517924, 0.12701810010931283], + [4.434785814273091, 0.12701811781862093], + [3.2441946767512198, 0.7326860643347705], + [3.2441943384465453, 0.7326862596551225], + [2.025625154565123, 1.4362278229269043], + [2.02562482636138, 1.4362280124154798] + ], + "i": [ + [0, 0], + [0.23412403720327402, -0.21685519252392726], + [2.3169569141385697e-8, -2.1460599171656693e-8], + [0.12082954450746661, -0.577327287429207], + [2.8932665020091746e-9, -1.3824118728109918e-8], + [-0.011731762179403275, -0.6814994661204397], + [0, 0], + [-0.06916516274213791, -0.29553642868995667], + [-0.4089892506599426, -0.38369056582450867], + [-0.5520287474808274, -0.3315116118779624], + [-2.06548226091432e-7, -1.2403914682636517e-7], + [-0.0003421609308178475, -0.00020547913065449563], + [-9.522279532319573e-8, -5.7184454171795934e-8], + [-0.300468962126192, -0.028193699262692107], + [-0.23482816288566433, 0.5132711436419459], + [-4.749490657134063e-9, 1.0381109561244399e-8], + [0.015839956566152225, 0.8717110845991289], + [1.69768109042726e-9, 9.342857912652088e-8], + [0.0000087126061476539, 0.00047948134514719243], + [3.601550327763895e-9, 1.9820203589113344e-7], + [-0.06519650465866711, 0.31268875228862747], + [-0.43304508924484253, 0.40126556158065796], + [-0.59089279815203, 0.3411521736162042], + [-0.0000014241099286293671, 8.222070948907651e-7], + [-0.0008213673055017523, 0.0004742148148251246], + [-6.723578905276919e-7, 3.8818609277855393e-7], + [-0.16870571202683138, 0.24084741490178985], + [0.30737975239753723, 0.4555366337299347], + [0.7087040779214693, 0.42583106358471445], + [2.7445777663474215e-7, 1.649100928279787e-7], + [0.0008585254429851474, 0.0005158516994262508], + [5.574174688230168e-7, 3.349291483792035e-7], + [0.3032451315899998, 0.09964292214652981], + [0.5636166848427546, -0.1743609069854332], + [1.9081610859760818e-8, -5.903103376763586e-9], + [0.5904353736717645, -0.3408880668133728], + [1.1276822138904231e-7, -6.510679230432561e-8], + [0.0005160650430098279, -0.00029795042572189345], + [1.094012526851895e-7, -6.316285158722189e-8] + ], + "o": [ + [-0.5902831360412986, 0.34080017551082276], + [-2.3169570721413733e-8, 2.1460597465806304e-8], + [-0.4327306209284364, 0.4008127362394225], + [-2.893266946272154e-9, 1.3824118635129576e-8], + [-0.06537346826988703, 0.3123564961622671], + [0, 0], + [0.011083245277404785, 0.6438270807266235], + [0.12779125571250916, 0.5460403561592102], + [0.22135944993610868, 0.2076669095871484], + [2.065482576719135e-7, 1.240390942389806e-7], + [0.0005038862064437044, 0.00030260059032016784], + [9.522278096150482e-8, 5.7184478086686534e-8], + [0.7474333287731865, 0.44885856155171666], + [0.5619706454667117, 0.05273100680431675], + [4.749491278485585e-9, -1.0381109276968093e-8], + [0.12555562034633994, -0.27443082741673663], + [-1.6977008275638342e-9, -9.342857876787732e-8], + [-0.000005982345874677719, -0.00032922677767110997], + [-3.6015087978602677e-9, -1.982020366457734e-7], + [-0.012396218379019608, -0.6821939155292132], + [0.12050330638885498, -0.5779455900192261], + [0.23429228444301842, -0.21709846620840878], + [0.0000014241085020977571, -8.222095657205253e-7], + [0.0011950908128373072, -0.0006899833542339806], + [6.723585719174632e-7, -3.881849125747127e-7], + [0.7160285544226506, -0.41339936776983066], + [0.3152824938297272, -0.45010316371917725], + [-0.16447704825847786, -0.243754891045721], + [-2.74457657246587e-7, -1.6491029152400852e-7], + [-0.0006024993709292659, -0.000362016439858337], + [-5.574176986283496e-7, -3.349287659173991e-7], + [-0.5843968181660015, -0.3511399257672008], + [-0.5604880953324063, -0.18417004929157346], + [-1.908161114992925e-8, 5.903102438802452e-9], + [-0.30493791944800464, 0.09433585430634558], + [-1.1276823037695247e-7, 6.510677673681311e-8], + [-0.0005239450472980245, 0.0003024999503683512], + [-1.0940124421810707e-7, 6.31628662526345e-8], + [0, 0] + ] + } + } + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 0.4666666666666667, 0.4666666666666667, 0.4666666666666667 + ], + "ix": 2 + }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "r": 1, + "bm": 0 + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [-8.689452171325684, 4.327548980712891], + "ix": 2 + }, + "a": { + "a": 0, + "k": [4.730164527893066, 5.6065826416015625], + "ix": 2 + }, + "s": { + "a": 1, + "k": [ + { + "t": 0, + "s": [100, 100], + "i": { "x": [0], "y": [0.75] }, + "o": { "x": [0.5], "y": [0.25] } + }, + { + "t": 125, + "s": [70, 70], + "i": { "x": [0], "y": [0.75] }, + "o": { "x": [0.5], "y": [0.25] } + }, + { + "t": 250, + "s": [100, 100], + "i": { "x": [0.75], "y": [0.75] }, + "o": { "x": [0.25], "y": [0.25] } + } + ], + "ix": 2 + }, + "r": { "a": 0, "k": 0, "ix": 2 }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "sk": { "a": 0, "k": 0, "ix": 2 }, + "sa": { "a": 0, "k": 0, "ix": 2 } + } + ] + }, + { + "ty": "tr", + "p": { "a": 0, "k": [15.999998092651367, 16], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 2 }, + "s": { "a": 0, "k": [100, 100], "ix": 2 }, + "r": { + "a": 1, + "k": [ + { + "t": 0, + "s": [0], + "i": { "x": [0], "y": [0.75] }, + "o": { "x": [0.5], "y": [0.25] } + }, + { + "t": 125, + "s": [120], + "i": { "x": [0], "y": [0.75] }, + "o": { "x": [0.5], "y": [0.25] } + }, + { + "t": 250, + "s": [420], + "i": { "x": [0.75], "y": [0.75] }, + "o": { "x": [0.25], "y": [0.25] } + } + ], + "ix": 2 + }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "sk": { "a": 0, "k": 0, "ix": 2 }, + "sa": { "a": 0, "k": 0, "ix": 2 } + } + ] + } + ], + "ip": 0, + "op": 251, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 2, + "ty": 4, + "nm": "Polygon 516", + "sr": 1, + "ks": { + "p": { "a": 0, "k": [0, 0], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 2 }, + "s": { "a": 0, "k": [100, 100], "ix": 2 }, + "r": { "a": 0, "k": 0, "ix": 2 }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "sk": { "a": 0, "k": 0, "ix": 2 }, + "sa": { "a": 0, "k": 0, "ix": 2 } + }, + "ao": 0, + "hd": true, + "shapes": [ + { + "ty": "gr", + "nm": "Polygon 516", + "it": [ + { + "ty": "sh", + "d": 1, + "ks": { + "a": 0, + "k": { + "c": true, + "v": [ + [15.47663223647195, 0], + [28.87978891828583, 7.738316118235975], + [28.879788918285833, 23.21494835470792], + [15.47663223647195, 30.9532644729439], + [2.073475554658072, 23.21494835470793], + [2.073475554658064, 7.738316118235985], + [15.47663223647195, 0] + ], + "i": [ + [0, 0], + [0, 0], + [0, 0], + [0, 0], + [0, 0], + [0, 0], + [0, 0] + ], + "o": [ + [0, 0], + [0, 0], + [0, 0], + [0, 0], + [0, 0], + [0, 0], + [0, 0] + ] + } + } + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 0.8509803921568627, 0.8509803921568627, 0.8509803921568627 + ], + "ix": 2 + }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "r": 1, + "bm": 0 + }, + { + "ty": "tr", + "p": { "a": 0, "k": [15.999999046325684, 16], "ix": 2 }, + "a": { + "a": 0, + "k": [15.47663223647195, 15.47663223647195], + "ix": 2 + }, + "s": { "a": 0, "k": [100, 100], "ix": 2 }, + "r": { "a": 0, "k": 0, "ix": 2 }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "sk": { "a": 0, "k": 0, "ix": 2 }, + "sa": { "a": 0, "k": 0, "ix": 2 } + } + ] + } + ], + "ip": 0, + "op": 251, + "st": 0, + "bm": 0 + } + ], + "markers": [] +} diff --git a/src/frontend/apps/conversations/src/components/CustomToast.tsx b/src/frontend/apps/conversations/src/components/CustomToast.tsx new file mode 100644 index 0000000..3100fa1 --- /dev/null +++ b/src/frontend/apps/conversations/src/components/CustomToast.tsx @@ -0,0 +1,131 @@ +import { useEffect, useState } from 'react'; +import { css } from 'styled-components'; + +import { Box, Text } from '@/components'; +import { Icon } from '@/components/Icon'; + +export type ToastType = 'success' | 'error' | 'info' | 'warning'; + +export interface ToastProps { + id: string; + message: string; + type: ToastType; + icon?: string; + duration?: number; + onClose: (id: string) => void; +} + +const getToastConfig = (type: ToastType) => { + switch (type) { + case 'success': + return { + icon: 'check_circle', + color: '#223E9E', + bgColor: '#EDF0FF', + borderColor: '#C8D3FF', + }; + case 'error': + return { + icon: 'error', + color: '#EF4444', + bgColor: '#FEF2F2', + borderColor: '#FECACA', + }; + case 'warning': + return { + icon: 'warning', + color: '#F59E0B', + bgColor: '#FFFBEB', + borderColor: '#FED7AA', + }; + case 'info': + return { + icon: 'info', + color: '#3B82F6', + bgColor: '#EFF6FF', + borderColor: '#BFDBFE', + }; + default: + return { + icon: 'info', + color: '#6B7280', + bgColor: '#F9FAFB', + borderColor: '#E5E7EB', + }; + } +}; + +export const Toast = ({ + id, + message, + type, + icon, + duration = 4000, + onClose, +}: ToastProps) => { + const [isVisible, setIsVisible] = useState(false); + const [isLeaving, setIsLeaving] = useState(false); + const config = getToastConfig(type); + const iconToUse = icon || config.icon; + + useEffect(() => { + const timer = setTimeout(() => setIsVisible(true), 100); + + const autoCloseTimer = setTimeout(() => { + setIsLeaving(true); + setTimeout(() => onClose(id), 300); + }, duration); + + return () => { + clearTimeout(timer); + clearTimeout(autoCloseTimer); + }; + }, [id, duration, onClose]); + + return ( + + + + + {message} + + + + ); +}; diff --git a/src/frontend/apps/conversations/src/components/Loader.tsx b/src/frontend/apps/conversations/src/components/Loader.tsx new file mode 100644 index 0000000..b262b78 --- /dev/null +++ b/src/frontend/apps/conversations/src/components/Loader.tsx @@ -0,0 +1,20 @@ +import Lottie from 'react-lottie'; + +import searchingAnimation from '@/assets/lotties/searching'; + +export const Loader = () => { + const LoaderOptions = { + loop: true, + autoplay: true, + animationData: searchingAnimation, + rendererSettings: { + preserveAspectRatio: 'xMidYMid slice', + }, + } as const; + + return ( +
+ +
+ ); +}; diff --git a/src/frontend/apps/conversations/src/components/SideModal.tsx b/src/frontend/apps/conversations/src/components/SideModal.tsx index d844304..db6f417 100644 --- a/src/frontend/apps/conversations/src/components/SideModal.tsx +++ b/src/frontend/apps/conversations/src/components/SideModal.tsx @@ -20,8 +20,6 @@ const SideModalStyle = createGlobalStyle` } & .c__modal{ - animation: slidein 0.7s; - width: ${({ width }) => width}; ${({ side }) => side === 'right' && 'left: auto;'}; diff --git a/src/frontend/apps/conversations/src/components/ToastProvider.tsx b/src/frontend/apps/conversations/src/components/ToastProvider.tsx new file mode 100644 index 0000000..b4eddf9 --- /dev/null +++ b/src/frontend/apps/conversations/src/components/ToastProvider.tsx @@ -0,0 +1,104 @@ +import { + ReactNode, + createContext, + useCallback, + useContext, + useState, +} from 'react'; +import { createPortal } from 'react-dom'; + +import { Box } from '@/components'; + +import { Toast, ToastType } from './CustomToast'; + +interface ToastItem { + id: string; + message: string; + type: ToastType; + icon?: string; + duration?: number; +} + +interface ToastContextType { + showToast: ( + type: ToastType, + message: string, + icon?: string, + duration?: number, + ) => void; +} + +const ToastContext = createContext(undefined); + +export const useToast = () => { + const context = useContext(ToastContext); + if (!context) { + throw new Error('useToast must be used within a ToastProvider'); + } + return context; +}; + +interface ToastProviderProps { + children: ReactNode; +} + +export const ToastProvider = ({ children }: ToastProviderProps) => { + const [toasts, setToasts] = useState([]); + + const showToast = useCallback( + (type: ToastType, message: string, icon?: string, duration = 4000) => { + const id = Math.random().toString(36).substr(2, 9); + const newToast: ToastItem = { id, message, type, icon, duration }; + + setToasts((prev) => [...prev, newToast]); + }, + [], + ); + + const removeToast = useCallback((id: string) => { + setToasts((prev) => prev.filter((toast) => toast.id !== id)); + }, []); + + const value = { + showToast, + }; + + return ( + + {children} + {typeof window !== 'undefined' && + createPortal( + * { + pointer-events: auto; + } + `} + > + {toasts.map((toast) => ( + + ))} + , + document.body, + )} + + ); +}; diff --git a/src/frontend/apps/conversations/src/components/index.ts b/src/frontend/apps/conversations/src/components/index.ts index 205b722..208df97 100644 --- a/src/frontend/apps/conversations/src/components/index.ts +++ b/src/frontend/apps/conversations/src/components/index.ts @@ -6,8 +6,11 @@ export * from './DropdownMenu'; export * from './Icon'; export * from './InfiniteScroll'; export * from './Link'; +export * from './Loader'; export * from './LoadMoreText'; export * from './SideModal'; export * from './separators'; export * from './Text'; export * from './TextErrors'; +export * from './CustomToast'; +export * from './ToastProvider'; diff --git a/src/frontend/apps/conversations/src/components/quick-search/QuickSearch.tsx b/src/frontend/apps/conversations/src/components/quick-search/QuickSearch.tsx index 2788792..cf3376f 100644 --- a/src/frontend/apps/conversations/src/components/quick-search/QuickSearch.tsx +++ b/src/frontend/apps/conversations/src/components/quick-search/QuickSearch.tsx @@ -24,6 +24,7 @@ export type QuickSearchData = { export type QuickSearchProps = { onFilter?: (str: string) => void; + onClear?: () => void; inputValue?: string; inputContent?: ReactNode; showInput?: boolean; @@ -35,6 +36,7 @@ export type QuickSearchProps = { export const QuickSearch = ({ onFilter, + onClear, inputContent, inputValue, loading, @@ -48,24 +50,23 @@ export const QuickSearch = ({ return ( <> -
- - {showInput && ( - - {inputContent} - - )} - - {children} - - -
+ + {showInput && ( + + {inputContent} + + )} + + {children} + + ); }; diff --git a/src/frontend/apps/conversations/src/components/quick-search/QuickSearchGroup.tsx b/src/frontend/apps/conversations/src/components/quick-search/QuickSearchGroup.tsx index 33cffcf..33a2a08 100644 --- a/src/frontend/apps/conversations/src/components/quick-search/QuickSearchGroup.tsx +++ b/src/frontend/apps/conversations/src/components/quick-search/QuickSearchGroup.tsx @@ -1,7 +1,11 @@ import { Command } from 'cmdk'; import { ReactNode } from 'react'; +import { useTranslation } from 'react-i18next'; + +import { useCunninghamTheme } from '@/cunningham'; import { Box } from '../Box'; +import { Text } from '../Text'; import { QuickSearchData } from './QuickSearch'; import { QuickSearchItem } from './QuickSearchItem'; @@ -17,13 +21,22 @@ export const QuickSearchGroup = ({ onSelect, renderElement, }: Props) => { + const { t } = useTranslation(); + const { spacingsTokens } = useCunninghamTheme(); + return ( - - + + + + {t('Search results')} + + {group.startActions?.map((action, index) => { return ( ({ ); })} {group.emptyString && group.elements.length === 0 && ( - {group.emptyString} + + {group.emptyString} + )} diff --git a/src/frontend/apps/conversations/src/components/quick-search/QuickSearchInput.tsx b/src/frontend/apps/conversations/src/components/quick-search/QuickSearchInput.tsx index 9ab52f5..8455d0b 100644 --- a/src/frontend/apps/conversations/src/components/quick-search/QuickSearchInput.tsx +++ b/src/frontend/apps/conversations/src/components/quick-search/QuickSearchInput.tsx @@ -1,4 +1,3 @@ -import { Loader } from '@openfun/cunningham-react'; import { Command } from 'cmdk'; import { ReactNode } from 'react'; import { useTranslation } from 'react-i18next'; @@ -8,6 +7,7 @@ import { useCunninghamTheme } from '@/cunningham'; import { Box } from '../Box'; import { Icon } from '../Icon'; +import { Loader } from '../Loader'; type Props = { loading?: boolean; @@ -16,6 +16,7 @@ type Props = { placeholder?: string; children?: ReactNode; withSeparator?: boolean; + onClear?: () => void; }; export const QuickSearchInput = ({ loading, @@ -24,6 +25,7 @@ export const QuickSearchInput = ({ placeholder, children, withSeparator: separator = true, + onClear, }: Props) => { const { t } = useTranslation(); const { spacingsTokens } = useCunninghamTheme(); @@ -42,16 +44,30 @@ export const QuickSearchInput = ({ {!loading && } - {loading && ( -
- -
- )} + {loading && } + 0 && onClear ? 1 : 0}; + transition: opacity 0.3s cubic-bezier(1, 0, 0, 1); + top: 10px; + right: 10px; + z-index: 1000;`} + onClick={onClear} + aria-label={t('Clear search')} + />
- {separator && } ); }; diff --git a/src/frontend/apps/conversations/src/components/quick-search/QuickSearchStyle.tsx b/src/frontend/apps/conversations/src/components/quick-search/QuickSearchStyle.tsx index b6fa0ad..80232d3 100644 --- a/src/frontend/apps/conversations/src/components/quick-search/QuickSearchStyle.tsx +++ b/src/frontend/apps/conversations/src/components/quick-search/QuickSearchStyle.tsx @@ -9,25 +9,18 @@ export const QuickSearchStyle = createGlobalStyle` overflow: hidden; transition: transform 100ms ease; outline: none; + border: none; } [cmdk-input] { border: none; - width: 100%; - font-size: 17px; - padding: 8px; - background: white; - outline: none; - color: var(--c--theme--colors--greyscale-1000); - border-radius: 0; &::placeholder { - color: var(--c--theme--colors--greyscale-500); + font-size: 14px; + color: #626A80; } } - - [cmdk-item] { content-visibility: auto; cursor: pointer; @@ -96,7 +89,7 @@ export const QuickSearchStyle = createGlobalStyle` [cmdk-separator] { height: 1px; width: 100%; - background: var(--c--theme--colors--greyscale-500); + background: transparent; margin: 4px 0; } @@ -105,18 +98,13 @@ export const QuickSearchStyle = createGlobalStyle` } [cmdk-group-heading] { + padding: 0 var(--c--theme--spacings--xs); user-select: none; - font-size: var(--c--theme--font--sizes--sm); + font-size: 12px; color: var(--c--theme--colors--greyscale-700); - font-weight: bold; - - display: flex; - align-items: center; + font-weight: 700;; margin-bottom: var(--c--theme--spacings--xs); } - - [cmdk-empty] { - } } .c__modal__scroller:has(.quick-search-container), diff --git a/src/frontend/apps/conversations/src/core/AppProvider.tsx b/src/frontend/apps/conversations/src/core/AppProvider.tsx index 03ce509..bf6a223 100644 --- a/src/frontend/apps/conversations/src/core/AppProvider.tsx +++ b/src/frontend/apps/conversations/src/core/AppProvider.tsx @@ -3,6 +3,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { useRouter } from 'next/router'; import { useEffect } from 'react'; +import { ToastProvider } from '@/components'; import { useCunninghamTheme } from '@/cunningham'; import { Auth, KEY_AUTH, setAuthUrl } from '@/features/auth'; import { useResponsiveStore } from '@/stores/'; @@ -63,7 +64,9 @@ export function AppProvider({ children }: { children: React.ReactNode }) { - {children} + + {children} + diff --git a/src/frontend/apps/conversations/src/cunningham/cunningham-tokens.css b/src/frontend/apps/conversations/src/cunningham/cunningham-tokens.css index 2fdb932..09d6f1b 100644 --- a/src/frontend/apps/conversations/src/cunningham/cunningham-tokens.css +++ b/src/frontend/apps/conversations/src/cunningham/cunningham-tokens.css @@ -19,8 +19,9 @@ --c--theme--colors--info-700: #273961; --c--theme--colors--info-800: #222a3f; --c--theme--colors--info-900: #1d2437; + --c--theme--colors--greyscale-050: #eaecf0; --c--theme--colors--greyscale-100: #eee; - --c--theme--colors--greyscale-200: #e5e5e5; + --c--theme--colors--greyscale-200: #d9dde6; --c--theme--colors--greyscale-300: #cecece; --c--theme--colors--greyscale-400: #929292; --c--theme--colors--greyscale-500: #7c7c7c; @@ -36,7 +37,7 @@ --c--theme--colors--primary-500: #6a6af4; --c--theme--colors--primary-600: #313178; --c--theme--colors--primary-700: #272747; - --c--theme--colors--primary-800: #000091; + --c--theme--colors--primary-800: #3e5de7; --c--theme--colors--primary-900: #21213f; --c--theme--colors--success-100: #dffee6; --c--theme--colors--success-200: #b8fec9; @@ -65,19 +66,18 @@ --c--theme--colors--danger-700: #642626; --c--theme--colors--danger-800: #412121; --c--theme--colors--danger-900: #391c1c; - --c--theme--colors--primary-text: #000091; + --c--theme--colors--primary-text: #4c6cef; --c--theme--colors--success-text: #1f8d49; --c--theme--colors--warning-text: #d64d00; --c--theme--colors--danger-text: #fff; --c--theme--colors--primary-050: #f5f5fe; --c--theme--colors--primary-150: #f4f4fd; --c--theme--colors--greyscale-text: #303c4b; - --c--theme--colors--greyscale-050: #f6f6f6; --c--theme--colors--greyscale-250: #ddd; --c--theme--colors--greyscale-350: #ddd; --c--theme--colors--greyscale-750: #353535; --c--theme--colors--greyscale-950: #1e1e1e; - --c--theme--colors--greyscale-1000: #161616; + --c--theme--colors--greyscale-1000: #2a2f3c; --c--theme--colors--danger-050: #fff4f4; --c--theme--colors--blue-500: #417dc4; --c--theme--colors--brown-500: #bd987a; @@ -205,10 +205,8 @@ --c--components--button--medium-text-height: 40px; --c--components--button--border-radius: 4px; --c--components--button--small-height: 26px; - --c--components--button--primary--background--color: var( - --c--theme--colors--primary-text - ); - --c--components--button--primary--background--color-hover: #1212ff; + --c--components--button--primary--background--color: #3e5de6; + --c--components--button--primary--background--color-hover: #2944c2; --c--components--button--primary--background--color-active: #2323ff; --c--components--button--primary--background--color-disabled: var( --c--theme--colors--greyscale-100 @@ -220,9 +218,7 @@ --c--components--button--primary--disabled: var( --c--theme--colors--greyscale-500 ); - --c--components--button--primary-text--background--color: var( - --c--theme--colors--primary-text - ); + --c--components--button--primary-text--background--color: #3e5de6; --c--components--button--primary-text--background--color-hover: var( --c--theme--colors--greyscale-100 ); @@ -491,7 +487,7 @@ --c--components--home-proconnect: false; --c--components--beta: false; --c--components--image-system-filter: ; - --c--components--favicon--ico: /assets/favicon-light.ico; + --c--components--favicon--ico: /assets/favicon.ico; --c--components--favicon--png-light: /assets/favicon-light.png; --c--components--favicon--png-dark: /assets/favicon-dark.png; } diff --git a/src/frontend/apps/conversations/src/cunningham/cunningham-tokens.ts b/src/frontend/apps/conversations/src/cunningham/cunningham-tokens.ts index a547d65..5456a90 100644 --- a/src/frontend/apps/conversations/src/cunningham/cunningham-tokens.ts +++ b/src/frontend/apps/conversations/src/cunningham/cunningham-tokens.ts @@ -28,6 +28,7 @@ export const tokens = { 'greyscale-300': '#CECECE', 'greyscale-400': '#929292', 'greyscale-500': '#7C7C7C', + 'greyscale-550': '#626A80', 'greyscale-600': '#666666', 'greyscale-700': '#3A3A3A', 'greyscale-800': '#2A2A2A', @@ -69,7 +70,7 @@ export const tokens = { 'danger-700': '#642626', 'danger-800': '#412121', 'danger-900': '#391C1C', - 'primary-text': '#000091', + 'primary-text': '#314ddf', 'success-text': '#1f8d49', 'warning-text': '#d64d00', 'danger-text': '#FFF', @@ -221,7 +222,7 @@ export const tokens = { 'border-radius': '4px', 'small-height': '26px', primary: { - 'background--color': '#000091', + 'background--color': '#3E5DE7', 'background--color-hover': '#1212ff', 'background--color-active': '#2323ff', 'background--color-disabled': '#eee', @@ -232,13 +233,13 @@ export const tokens = { disabled: '#7C7C7C', }, 'primary-text': { - 'background--color': '#000091', + 'background--color': '#3E5DE7', 'background--color-hover': '#eee', 'background--color-active': '#ECECFE', 'background--color-focus-visible': '#fff', 'background--color-disabled': '#fff', - color: '#000091', - 'color-hover': '#000091', + color: '#3E5DE7', + 'color-hover': '#3E5DE7', disabled: '#929292', }, secondary: { @@ -268,11 +269,11 @@ export const tokens = { }, danger: { 'color-hover': 'white', - 'background--color': '#CE0500', + 'background--color': '#626A80', 'background--color-hover': '#FF2725', 'background--color-focus-visible': '#CE0500', 'background--color-disabled': '#eee', - 'color-disabled': '#929292', + 'color-disabled': '#626A80', }, }, datagrid: { @@ -381,7 +382,7 @@ export const tokens = { 'greyscale-200': '#303C4B', 'greyscale-300': '#555F6B', 'greyscale-400': '#79818A', - 'greyscale-500': '#9EA3AA', + 'greyscale-500': '#6D778C', 'greyscale-600': '#C2C6CA', 'greyscale-700': '#E7E8EA', 'greyscale-800': '#F3F4F4', diff --git a/src/frontend/apps/conversations/src/features/auth/components/Auth.tsx b/src/frontend/apps/conversations/src/features/auth/components/Auth.tsx index addb481..d32463f 100644 --- a/src/frontend/apps/conversations/src/features/auth/components/Auth.tsx +++ b/src/frontend/apps/conversations/src/features/auth/components/Auth.tsx @@ -1,8 +1,7 @@ -import { Loader } from '@openfun/cunningham-react'; import { useRouter } from 'next/router'; import { PropsWithChildren } from 'react'; -import { Box } from '@/components'; +import { Box, Loader } from '@/components'; import { useConfig } from '@/core'; import { HOME_URL } from '../conf'; diff --git a/src/frontend/apps/conversations/src/features/auth/components/ButtonLogin.tsx b/src/frontend/apps/conversations/src/features/auth/components/ButtonLogin.tsx index e3c123d..0833060 100644 --- a/src/frontend/apps/conversations/src/features/auth/components/ButtonLogin.tsx +++ b/src/frontend/apps/conversations/src/features/auth/components/ButtonLogin.tsx @@ -45,7 +45,7 @@ export const ProConnectButton = () => { onClick={() => gotoLogin()} aria-label={t('Proconnect Login')} $css={css` - background-color: var(--c--theme--colors--primary-text); + background-color: #000091; &:hover { background-color: var(--c--theme--colors--primary-action); } diff --git a/src/frontend/apps/conversations/src/features/chat-search/assets/illustration-docs-empty.png b/src/frontend/apps/conversations/src/features/chat-search/assets/illustration-docs-empty.png deleted file mode 100644 index c3dbb6e..0000000 Binary files a/src/frontend/apps/conversations/src/features/chat-search/assets/illustration-docs-empty.png and /dev/null differ diff --git a/src/frontend/apps/conversations/src/features/chat-search/components/ConversationSearchItem.tsx b/src/frontend/apps/conversations/src/features/chat-search/components/ConversationSearchItem.tsx deleted file mode 100644 index a10a8f6..0000000 --- a/src/frontend/apps/conversations/src/features/chat-search/components/ConversationSearchItem.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { Box, Icon } from '@/components'; -import { QuickSearchItemContent } from '@/components/quick-search/'; -import { ChatConversation } from '@/features/chat/types'; -import { SimpleConversationItem } from '@/features/left-panel/components/SimpleConversationItem'; -import { useResponsiveStore } from '@/stores'; - -type ConversationSearchItemProps = { - conversation: ChatConversation; -}; - -export const ConversationSearchItem = ({ - conversation, -}: ConversationSearchItemProps) => { - const { isDesktop } = useResponsiveStore(); - return ( - - - - - - - } - right={ - - } - /> -
- ); -}; diff --git a/src/frontend/apps/conversations/src/features/chat-search/components/ConversationSearchModal.tsx b/src/frontend/apps/conversations/src/features/chat-search/components/ConversationSearchModal.tsx deleted file mode 100644 index 11b6117..0000000 --- a/src/frontend/apps/conversations/src/features/chat-search/components/ConversationSearchModal.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import { Modal, ModalSize } from '@openfun/cunningham-react'; -import Image from 'next/image'; -import { useRouter } from 'next/navigation'; -import { useMemo, useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { InView } from 'react-intersection-observer'; -import { useDebouncedCallback } from 'use-debounce'; - -import { Box } from '@/components'; -import { - QuickSearch, - QuickSearchData, - QuickSearchGroup, -} from '@/components/quick-search'; -import { useInfiniteConversations } from '@/features/chat/api/useConversations'; -import { ChatConversation } from '@/features/chat/types'; -import { useResponsiveStore } from '@/stores'; - -import EmptySearchIcon from '../assets/illustration-docs-empty.png'; - -import { ConversationSearchItem } from './ConversationSearchItem'; - -type ConversationSearchModalProps = { - onClose: () => void; - isOpen: boolean; -}; - -export const ConversationSearchModal = ({ - ...modalProps -}: ConversationSearchModalProps) => { - const { t } = useTranslation(); - const router = useRouter(); - const [search, setSearch] = useState(''); - const { isDesktop } = useResponsiveStore(); - const { - data, - isFetching, - isRefetching, - isLoading, - fetchNextPage, - hasNextPage, - } = useInfiniteConversations({ - page: 1, - title: search, - }); - const loading = isFetching || isRefetching || isLoading; - const handleInputSearch = useDebouncedCallback(setSearch, 300); - - const handleSelect = (conversation: ChatConversation) => { - router.push(`/chat/${conversation.id}`); - modalProps.onClose?.(); - }; - - const conversationsData: QuickSearchData = useMemo(() => { - const conversations = data?.pages.flatMap((page) => page.results) || []; - - return { - groupName: conversations.length > 0 ? t('Select a conversation') : '', - elements: search ? conversations : [], - emptyString: t('No conversation found'), - endActions: hasNextPage - ? [{ content: void fetchNextPage()} /> }] - : [], - }; - }, [data, hasNextPage, fetchNextPage, t, search]); - - return ( - - - - - {search.length === 0 && ( - - {t('No - - )} - {search && ( - ( - - )} - /> - )} - - - - - ); -}; diff --git a/src/frontend/apps/conversations/src/features/chat-search/components/index.ts b/src/frontend/apps/conversations/src/features/chat-search/components/index.ts deleted file mode 100644 index e2720f8..0000000 --- a/src/frontend/apps/conversations/src/features/chat-search/components/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './ConversationSearchModal'; diff --git a/src/frontend/apps/conversations/src/features/chat-search/index.ts b/src/frontend/apps/conversations/src/features/chat-search/index.ts deleted file mode 100644 index 07635cb..0000000 --- a/src/frontend/apps/conversations/src/features/chat-search/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './components'; diff --git a/src/frontend/apps/conversations/src/features/chat/api/useChat.tsx b/src/frontend/apps/conversations/src/features/chat/api/useChat.tsx index be53da7..c52d877 100644 --- a/src/frontend/apps/conversations/src/features/chat/api/useChat.tsx +++ b/src/frontend/apps/conversations/src/features/chat/api/useChat.tsx @@ -14,6 +14,14 @@ const fetchAPIAdapter = (input: RequestInfo | URL, init?: RequestInit) => { } else { throw new Error('Unsupported input type for fetchAPIAdapter'); } + + // Add force_web_search parameter if it's globally enabled + if ((window as { globalForceWebSearch?: boolean }).globalForceWebSearch) { + const urlObj = new URL(url); + urlObj.searchParams.set('force_web_search', 'true'); + url = urlObj.toString(); + } + return fetchAPI(url, init); }; diff --git a/src/frontend/apps/conversations/src/features/chat/components/Chat.tsx b/src/frontend/apps/conversations/src/features/chat/components/Chat.tsx index 10281a7..bd0e183 100644 --- a/src/frontend/apps/conversations/src/features/chat/components/Chat.tsx +++ b/src/frontend/apps/conversations/src/features/chat/components/Chat.tsx @@ -4,26 +4,27 @@ import { SourceUIPart, ToolInvocationUIPart, } from '@ai-sdk/ui-utils'; -import { Loader, Modal, ModalSize } from '@openfun/cunningham-react'; +import { Modal, ModalSize } from '@openfun/cunningham-react'; import 'katex/dist/katex.min.css'; // `rehype-katex` does not import the CSS for you -import Image from 'next/image'; import { useRouter } from 'next/router'; -import { useEffect, useRef, useState } from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import Markdown from 'react-markdown'; import rehypeKatex from 'rehype-katex'; import remarkGfm from 'remark-gfm'; import remarkMath from 'remark-math'; -import { Box, BoxButton, Icon, Text } from '@/components'; -import { DropdownMenu } from '@/components/DropdownMenu'; -import { useConfig } from '@/core'; -import { useAuth } from '@/features/auth'; +import { APIError, errorCauses, fetchAPI } from '@/api'; +import { Box, Icon, Loader, Text } from '@/components'; import { useChat } from '@/features/chat/api/useChat'; import { getConversation } from '@/features/chat/api/useConversation'; import { useCreateChatConversation } from '@/features/chat/api/useCreateConversation'; -import SourceItemList from '@/features/chat/components/SourceItemList'; +import { InputChat } from '@/features/chat/components/InputChat'; +import { SourceItemList } from '@/features/chat/components/SourceItemList'; import { ToolInvocationItem } from '@/features/chat/components/ToolInvocationItem'; +import { setChatContainerRef } from '@/features/chat/hooks/useChatScroll'; +import { useClipboard } from '@/hook'; +import { useResponsiveStore } from '@/stores'; import { usePendingChatStore } from '../stores/usePendingChatStore'; @@ -40,28 +41,39 @@ export const Chat = ({ initialConversationId: string | undefined; }) => { const { t } = useTranslation(); + const copyToClipboard = useClipboard(); + const { isDesktop } = useResponsiveStore(); const streamProtocol = 'data'; // or 'text' + const [forceWebSearch, setForceWebSearch] = useState(false); const apiUrl = `chats/${initialConversationId}/conversation/?protocol=${streamProtocol}`; const router = useRouter(); - const { user } = useAuth(); - const { data: conf } = useConfig(); const [files, setFiles] = useState(null); + const fileInputRef = useRef(null); - const [isDragActive, setIsDragActive] = useState(false); + const chatContainerRef = useRef(null); const [conversationId, setConversationId] = useState(initialConversationId); const [chatErrorModal, setChatErrorModal] = useState<{ title: string; message: string; } | null>(null); + const [isSourceOpen, setIsSourceOpen] = useState(null); + + // Définir la ref globale pour le hook useChatScroll + useEffect(() => { + setChatContainerRef(chatContainerRef); + }, []); + const [initialConversationMessages, setInitialConversationMessages] = useState(undefined); const [pendingFirstMessage, setPendingFirstMessage] = useState<{ event: React.FormEvent; attachments?: FileList | null; + forceWebSearch?: boolean; } | null>(null); const [shouldAutoSubmit, setShouldAutoSubmit] = useState(false); + const [hasInitialized, setHasInitialized] = useState(false); const { mutate: createChatConversation } = useCreateChatConversation(); @@ -90,6 +102,7 @@ export const Chat = ({ handleSubmit: baseHandleSubmit, handleInputChange, status, + stop: stopChat, } = useChat({ id: conversationId, initialMessages: initialConversationMessages, @@ -99,10 +112,104 @@ export const Chat = ({ onError: onErrorChat, }); + // Scroll to bottom when new messages arrive + const scrollToBottom = useCallback(() => { + if (chatContainerRef.current) { + chatContainerRef.current.scrollTo({ + top: chatContainerRef.current.scrollHeight, + behavior: hasInitialized ? 'smooth' : 'auto', + }); + } + }, [hasInitialized]); + + const stopGeneration = async () => { + stopChat(); + + const response = await fetchAPI(`chats/${conversationId}/stop-steaming/`, { + method: 'POST', + }); + + if (!response.ok) { + throw new APIError( + 'Failed to stop the conversation', + await errorCauses(response), + ); + } + }; + + const toggleWebSearch = () => { + setForceWebSearch((prev) => { + const newValue = !prev; + // Update global state for the fetch adapter + (window as { globalForceWebSearch?: boolean }).globalForceWebSearch = + newValue; + return newValue; + }); + }; + + const handleStop = () => { + void stopGeneration(); + }; + + const handleSubmitWrapper = (event: React.FormEvent) => { + void handleSubmit(event); + }; + + const openSources = (messageId: string) => { + if (isSourceOpen === messageId) { + setIsSourceOpen(null); + return; + } + const message = messages.find((msg) => msg.id === messageId); + if (message?.parts) { + const sourceParts = message.parts.filter( + (part): part is SourceUIPart => part.type === 'source', + ); + if (sourceParts.length > 0) { + setIsSourceOpen(messageId); + } + } + }; + + useEffect(() => { + if (chatContainerRef.current) { + // Find the last user message + const userMessages = messages.filter((msg) => msg.role === 'user'); + const lastUserMessage = userMessages[userMessages.length - 1]; + + if (lastUserMessage) { + // Find the element of the last user message + const messageElements = + chatContainerRef.current.querySelectorAll('[data-message-id]'); + const lastUserMessageElement = Array.from(messageElements).find( + (el) => el.getAttribute('data-message-id') === lastUserMessage.id, + ); + + if (lastUserMessageElement) { + // Scroll to position the last user message at the very top + const messageTop = (lastUserMessageElement as HTMLElement).offsetTop; + + chatContainerRef.current.scrollTo({ + top: messageTop, + behavior: 'auto', + }); + } + } else { + scrollToBottom(); + } + } + }, [messages, hasInitialized, scrollToBottom]); + // Synchronize conversationId state with prop when it changes (e.g., after navigation) useEffect(() => { setConversationId(initialConversationId); - }, [initialConversationId]); + // Reset input when conversation changes + if (initialConversationId !== conversationId) { + handleInputChange({ + target: { value: '' }, + } as React.ChangeEvent); + } + }, [initialConversationId, conversationId, handleInputChange]); // On mount, if there is pending input/files, initialize state and set flag useEffect(() => { @@ -136,7 +243,7 @@ export const Chat = ({ preventDefault: () => {}, target: form, } as unknown as React.FormEvent; - handleSubmit(syntheticFormEvent); + void handleSubmit(syntheticFormEvent); setShouldAutoSubmit(false); } // eslint-disable-next-line react-hooks/exhaustive-deps @@ -153,11 +260,13 @@ export const Chat = ({ }); if (!ignore) { setInitialConversationMessages(conversation.messages); + setHasInitialized(true); } } catch { // Optionally handle error (e.g., setInitialConversationMessages([]) or show error) if (!ignore) { setInitialConversationMessages([]); + setHasInitialized(true); } } } @@ -170,11 +279,32 @@ export const Chat = ({ }, [initialConversationId, pendingInput]); // Custom handleSubmit to include attachments and handle chat creation - const handleSubmit = (event: React.FormEvent) => { + const handleSubmit = async (event: React.FormEvent) => { event.preventDefault(); + + // Convert files to base64 if they exist + let attachments: Attachment[] = []; + if (files && files.length > 0) { + attachments = await Promise.all( + Array.from(files).map(async (file) => { + return new Promise((resolve) => { + const reader = new FileReader(); + reader.onload = () => { + resolve({ + name: file.name, + contentType: file.type, + url: reader.result as string, + }); + }; + reader.readAsDataURL(file); + }); + }), + ); + } + if (!conversationId) { // Save the event and files, then create the chat - setPendingFirstMessage({ event, attachments: files }); + setPendingFirstMessage({ event, attachments: files, forceWebSearch }); // Save input and files to Zustand store before navigation setPendingChat(input, files); void createChatConversation( @@ -187,13 +317,18 @@ export const Chat = ({ // After setting the conversationId, submit the pending message setTimeout(() => { if (pendingFirstMessage) { + // Prepare options with attachments + const options: Record = {}; if ( pendingFirstMessage.attachments && pendingFirstMessage.attachments.length > 0 ) { - baseHandleSubmit(pendingFirstMessage.event, { - experimental_attachments: pendingFirstMessage.attachments, - }); + options.experimental_attachments = + pendingFirstMessage.attachments; + } + + if (Object.keys(options).length > 0) { + baseHandleSubmit(pendingFirstMessage.event, options); } else { baseHandleSubmit(pendingFirstMessage.event); } @@ -209,132 +344,290 @@ export const Chat = ({ ); return; } - if (files && files.length > 0) { - baseHandleSubmit(event, { experimental_attachments: files }); + + // Prepare options with attachments + const options: Record = {}; + if (attachments.length > 0) { + options.experimental_attachments = attachments; + } + + if (Object.keys(options).length > 0) { + baseHandleSubmit(event, options); } else { baseHandleSubmit(event); } - setFiles(null); - if (fileInputRef.current) { - fileInputRef.current.value = ''; - } + + // Attendre un peu avant de vider les fichiers pour s'assurer qu'ils sont traités + setTimeout(() => { + setFiles(null); + if (fileInputRef.current) { + fileInputRef.current.value = ''; + } + }, 100); }; return ( - + 1 ? 'calc(100vh - 62px)' : '0'}; + max-height: ${messages.length > 1 ? 'calc(100vh - 62px)' : '0'}; + `} > - {messages.map((message) => ( - - - {`${message.role === 'user' ? user?.full_name : message.role}:`} - - - {message.content && ( - ( - - ), - }} + {messages.length > 0 && ( + + {messages.map((message) => ( + + - {message.content} - - )} - - {message.parts - ?.filter( - (part) => - part.type === 'reasoning' || - part.type === 'tool-invocation', - ) - .map((part: ReasoningUIPart | ToolInvocationUIPart) => - part.type === 'reasoning' ? ( - - {part.reasoning} - - ) : part.type === 'tool-invocation' ? ( - - ) : null, + {message.content && ( + , + }} + > + {message.content} + )} - {/* Show attachments if present */} - {message.experimental_attachments?.map( - (attachment: Attachment, index: number) => - attachment.contentType?.includes('image/') ? ( - {attachment.name - ) : attachment.contentType?.includes('text/') ? ( -
- {attachment.url} -
- ) : null, - )} - {/* Show sources if present */} - {message.parts && ( - part.type === 'source', + + {message.parts + ?.filter( + (part) => + part.type === 'reasoning' || + part.type === 'tool-invocation', + ) + .map((part: ReasoningUIPart | ToolInvocationUIPart) => + part.type === 'reasoning' ? ( + + {part.reasoning} + + ) : part.type === 'tool-invocation' ? ( + + ) : null, + )} + {/* Show attachments if present */} + {message.experimental_attachments?.map( + (attachment: Attachment, index: number) => + attachment.contentType?.includes('text/') || + attachment.contentType?.includes('image/') ? ( +
+ {attachment.name} +
+ ) : null, )} - /> - )} +
+ {message.role !== 'user' && ( + + copyToClipboard(message.content)} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + copyToClipboard(message.content); + } + }} + role="button" + tabIndex={0} + > + + {isDesktop && ( + + {t('Copy')} + + )} + + {message.parts?.some( + (part) => part.type === 'source', + ) && ( + openSources(message.id)} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + openSources(message.id); + } + }} + role="button" + tabIndex={0} + > + + {isDesktop && ( + + {t('Show sources')} + + )} + + )} + + )} + {message.parts && isSourceOpen === message.id && ( + part.type === 'source', + )} + /> + )} +
-
+ ))}
- ))} + )} {(status === 'streaming' || status === 'submitted') && ( - - {t('Generating...')} + + + {t('Thinking...')} + )}
- + + + { @@ -348,253 +641,6 @@ export const Chat = ({ > {chatErrorModal?.message} - - {messages.length === 0 && ( - - - {t('Welcome to your assistant!')} - - - {t('Start a conversation by typing a message below.')} - - - )} - - {/* File preview */} - {files && files.length > 0 && ( - - {Array.from(files).map((file, idx) => { - const { type, name } = file; - const removeFile = () => { - if (!files) { - return; - } - const dt = new DataTransfer(); - Array.from(files).forEach((f, i) => { - if (i !== idx) { - dt.items.add(f); - } - }); - setFiles(dt.files.length > 0 ? dt.files : null); - }; - if (type.startsWith('image/')) { - return ( - - {name} - - {name} - - - - - - ); - } else if (type.startsWith('text/')) { - return ( - - - - {name} - - - - - - ); - } - return null; - })} - - )} - -
{ - e.preventDefault(); - setIsDragActive(true); - }} - onDragLeave={(e) => { - e.preventDefault(); - setIsDragActive(false); - }} - onDrop={(e) => { - e.preventDefault(); - setIsDragActive(false); - if (e.dataTransfer.files && e.dataTransfer.files.length > 0) { - setFiles((prev) => { - if (!prev || prev.length === 0) { - return e.dataTransfer.files; - } - const dt = new DataTransfer(); - Array.from(prev).forEach((f) => dt.items.add(f)); - Array.from(e.dataTransfer.files).forEach((f) => { - if ( - !Array.from(prev).some( - (pf) => - pf.name === f.name && - pf.size === f.size && - pf.lastModified === f.lastModified, - ) - ) { - dt.items.add(f); - } - }); - return dt.files; - }); - } - }} - > - - -