@memori.ai/memori-react 8.4.1 → 8.5.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.
Files changed (143) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/components/LoginDrawer/LoginDrawer.d.ts +1 -2
  3. package/dist/components/LoginDrawer/LoginDrawer.js +2 -105
  4. package/dist/components/LoginDrawer/LoginDrawer.js.map +1 -1
  5. package/dist/components/MemoriArtifactSystem/components/ArtifactActions/ArtifactActions.css +0 -4
  6. package/dist/components/MemoriArtifactSystem/components/ArtifactActions/ArtifactActions.d.ts +1 -0
  7. package/dist/components/MemoriArtifactSystem/components/ArtifactActions/ArtifactActions.js +3 -3
  8. package/dist/components/MemoriArtifactSystem/components/ArtifactActions/ArtifactActions.js.map +1 -1
  9. package/dist/components/MemoriArtifactSystem/components/ArtifactActions/components/CopyButtonWithDropdown.js +110 -8
  10. package/dist/components/MemoriArtifactSystem/components/ArtifactActions/components/CopyButtonWithDropdown.js.map +1 -1
  11. package/dist/components/MemoriArtifactSystem/components/ArtifactActions/components/CopyMenuItem.js +3 -0
  12. package/dist/components/MemoriArtifactSystem/components/ArtifactActions/components/CopyMenuItem.js.map +1 -1
  13. package/dist/components/MemoriArtifactSystem/components/ArtifactActions/hooks/useCopyArtifact.js +42 -38
  14. package/dist/components/MemoriArtifactSystem/components/ArtifactActions/hooks/useCopyArtifact.js.map +1 -1
  15. package/dist/components/MemoriArtifactSystem/components/ArtifactActions/styles.css +1 -2
  16. package/dist/components/MemoriArtifactSystem/components/ArtifactActions/types.d.ts +2 -1
  17. package/dist/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.css +428 -15
  18. package/dist/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.d.ts +1 -0
  19. package/dist/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.js +47 -8
  20. package/dist/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.js.map +1 -1
  21. package/dist/components/MemoriArtifactSystem/components/ArtifactDrawer/components/TabSwitch.css +150 -0
  22. package/dist/components/MemoriArtifactSystem/components/ArtifactDrawer/components/TabSwitch.d.ts +9 -0
  23. package/dist/components/MemoriArtifactSystem/components/ArtifactDrawer/components/TabSwitch.js +35 -0
  24. package/dist/components/MemoriArtifactSystem/components/ArtifactDrawer/components/TabSwitch.js.map +1 -0
  25. package/dist/components/MemoriArtifactSystem/components/ArtifactHandler/ArtifactHandler.css +2 -0
  26. package/dist/components/MemoriArtifactSystem/components/ArtifactPreview/ArtifactPreview.css +1 -1
  27. package/dist/components/MemoriArtifactSystem/components/ArtifactPreview/ArtifactPreview.d.ts +2 -1
  28. package/dist/components/MemoriArtifactSystem/components/ArtifactPreview/ArtifactPreview.js +15 -26
  29. package/dist/components/MemoriArtifactSystem/components/ArtifactPreview/ArtifactPreview.js.map +1 -1
  30. package/dist/components/MemoriArtifactSystem/context/ArtifactContext.js +1 -5
  31. package/dist/components/MemoriArtifactSystem/context/ArtifactContext.js.map +1 -1
  32. package/dist/components/MemoriWidget/MemoriWidget.js +1 -1
  33. package/dist/components/MemoriWidget/MemoriWidget.js.map +1 -1
  34. package/dist/components/icons/Close.d.ts +2 -1
  35. package/dist/components/icons/Close.js +1 -1
  36. package/dist/components/icons/Close.js.map +1 -1
  37. package/dist/components/icons/MenuVertical.d.ts +7 -0
  38. package/dist/components/icons/MenuVertical.js +6 -0
  39. package/dist/components/icons/MenuVertical.js.map +1 -0
  40. package/dist/components/layouts/Chat.js +1 -1
  41. package/dist/components/layouts/Chat.js.map +1 -1
  42. package/dist/components/layouts/FullPage.js +1 -1
  43. package/dist/components/layouts/FullPage.js.map +1 -1
  44. package/dist/components/layouts/ZoomedFullBody.js +1 -1
  45. package/dist/components/layouts/ZoomedFullBody.js.map +1 -1
  46. package/dist/components/layouts/chat.css +73 -85
  47. package/dist/components/ui/Drawer.d.ts +1 -0
  48. package/dist/components/ui/Drawer.js +2 -2
  49. package/dist/components/ui/Drawer.js.map +1 -1
  50. package/dist/locales/de.json +37 -1
  51. package/dist/locales/en.json +37 -1
  52. package/dist/locales/es.json +37 -1
  53. package/dist/locales/fr.json +37 -1
  54. package/dist/locales/it.json +37 -1
  55. package/esm/components/LoginDrawer/LoginDrawer.d.ts +1 -2
  56. package/esm/components/LoginDrawer/LoginDrawer.js +3 -106
  57. package/esm/components/LoginDrawer/LoginDrawer.js.map +1 -1
  58. package/esm/components/MemoriArtifactSystem/components/ArtifactActions/ArtifactActions.css +0 -4
  59. package/esm/components/MemoriArtifactSystem/components/ArtifactActions/ArtifactActions.d.ts +1 -0
  60. package/esm/components/MemoriArtifactSystem/components/ArtifactActions/ArtifactActions.js +3 -3
  61. package/esm/components/MemoriArtifactSystem/components/ArtifactActions/ArtifactActions.js.map +1 -1
  62. package/esm/components/MemoriArtifactSystem/components/ArtifactActions/components/CopyButtonWithDropdown.js +110 -8
  63. package/esm/components/MemoriArtifactSystem/components/ArtifactActions/components/CopyButtonWithDropdown.js.map +1 -1
  64. package/esm/components/MemoriArtifactSystem/components/ArtifactActions/components/CopyMenuItem.js +3 -0
  65. package/esm/components/MemoriArtifactSystem/components/ArtifactActions/components/CopyMenuItem.js.map +1 -1
  66. package/esm/components/MemoriArtifactSystem/components/ArtifactActions/hooks/useCopyArtifact.js +42 -38
  67. package/esm/components/MemoriArtifactSystem/components/ArtifactActions/hooks/useCopyArtifact.js.map +1 -1
  68. package/esm/components/MemoriArtifactSystem/components/ArtifactActions/styles.css +1 -2
  69. package/esm/components/MemoriArtifactSystem/components/ArtifactActions/types.d.ts +2 -1
  70. package/esm/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.css +428 -15
  71. package/esm/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.d.ts +1 -0
  72. package/esm/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.js +48 -9
  73. package/esm/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.js.map +1 -1
  74. package/esm/components/MemoriArtifactSystem/components/ArtifactDrawer/components/TabSwitch.css +150 -0
  75. package/esm/components/MemoriArtifactSystem/components/ArtifactDrawer/components/TabSwitch.d.ts +9 -0
  76. package/esm/components/MemoriArtifactSystem/components/ArtifactDrawer/components/TabSwitch.js +32 -0
  77. package/esm/components/MemoriArtifactSystem/components/ArtifactDrawer/components/TabSwitch.js.map +1 -0
  78. package/esm/components/MemoriArtifactSystem/components/ArtifactHandler/ArtifactHandler.css +2 -0
  79. package/esm/components/MemoriArtifactSystem/components/ArtifactPreview/ArtifactPreview.css +1 -1
  80. package/esm/components/MemoriArtifactSystem/components/ArtifactPreview/ArtifactPreview.d.ts +2 -1
  81. package/esm/components/MemoriArtifactSystem/components/ArtifactPreview/ArtifactPreview.js +16 -27
  82. package/esm/components/MemoriArtifactSystem/components/ArtifactPreview/ArtifactPreview.js.map +1 -1
  83. package/esm/components/MemoriArtifactSystem/context/ArtifactContext.js +1 -5
  84. package/esm/components/MemoriArtifactSystem/context/ArtifactContext.js.map +1 -1
  85. package/esm/components/MemoriWidget/MemoriWidget.js +1 -1
  86. package/esm/components/MemoriWidget/MemoriWidget.js.map +1 -1
  87. package/esm/components/icons/Close.d.ts +2 -1
  88. package/esm/components/icons/Close.js +1 -1
  89. package/esm/components/icons/Close.js.map +1 -1
  90. package/esm/components/icons/MenuVertical.d.ts +7 -0
  91. package/esm/components/icons/MenuVertical.js +4 -0
  92. package/esm/components/icons/MenuVertical.js.map +1 -0
  93. package/esm/components/layouts/Chat.js +1 -1
  94. package/esm/components/layouts/Chat.js.map +1 -1
  95. package/esm/components/layouts/FullPage.js +1 -1
  96. package/esm/components/layouts/FullPage.js.map +1 -1
  97. package/esm/components/layouts/ZoomedFullBody.js +1 -1
  98. package/esm/components/layouts/ZoomedFullBody.js.map +1 -1
  99. package/esm/components/layouts/chat.css +73 -85
  100. package/esm/components/ui/Drawer.d.ts +1 -0
  101. package/esm/components/ui/Drawer.js +2 -2
  102. package/esm/components/ui/Drawer.js.map +1 -1
  103. package/esm/locales/de.json +37 -1
  104. package/esm/locales/en.json +37 -1
  105. package/esm/locales/es.json +37 -1
  106. package/esm/locales/fr.json +37 -1
  107. package/esm/locales/it.json +37 -1
  108. package/package.json +1 -1
  109. package/src/components/FilePreview/__snapshots__/FilePreview.test.tsx.snap +9 -0
  110. package/src/components/LoginDrawer/LoginDrawer.tsx +46 -221
  111. package/src/components/MemoriArtifactSystem/components/ArtifactActions/ArtifactActions.css +0 -4
  112. package/src/components/MemoriArtifactSystem/components/ArtifactActions/ArtifactActions.tsx +7 -5
  113. package/src/components/MemoriArtifactSystem/components/ArtifactActions/components/CopyButtonWithDropdown.tsx +148 -12
  114. package/src/components/MemoriArtifactSystem/components/ArtifactActions/components/CopyMenuItem.tsx +3 -0
  115. package/src/components/MemoriArtifactSystem/components/ArtifactActions/hooks/useCopyArtifact.ts +54 -47
  116. package/src/components/MemoriArtifactSystem/components/ArtifactActions/styles.css +1 -2
  117. package/src/components/MemoriArtifactSystem/components/ArtifactActions/types.ts +2 -1
  118. package/src/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.css +428 -15
  119. package/src/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.tsx +240 -42
  120. package/src/components/MemoriArtifactSystem/components/ArtifactDrawer/components/TabSwitch.css +150 -0
  121. package/src/components/MemoriArtifactSystem/components/ArtifactDrawer/components/TabSwitch.tsx +79 -0
  122. package/src/components/MemoriArtifactSystem/components/ArtifactHandler/ArtifactHandler.css +2 -0
  123. package/src/components/MemoriArtifactSystem/components/ArtifactPreview/ArtifactPreview.css +1 -1
  124. package/src/components/MemoriArtifactSystem/components/ArtifactPreview/ArtifactPreview.tsx +2 -41
  125. package/src/components/MemoriArtifactSystem/context/ArtifactContext.tsx +1 -5
  126. package/src/components/MemoriWidget/MemoriWidget.tsx +0 -1
  127. package/src/components/icons/Close.tsx +8 -1
  128. package/src/components/icons/MenuVertical.tsx +29 -0
  129. package/src/components/layouts/Chat.tsx +3 -1
  130. package/src/components/layouts/FullPage.tsx +7 -2
  131. package/src/components/layouts/ZoomedFullBody.tsx +8 -3
  132. package/src/components/layouts/__snapshots__/Chat.test.tsx.snap +1 -1
  133. package/src/components/layouts/__snapshots__/FullPage.test.tsx.snap +112 -104
  134. package/src/components/layouts/__snapshots__/HiddenChat.test.tsx.snap +3 -0
  135. package/src/components/layouts/__snapshots__/ZoomedFullBody.test.tsx.snap +56 -52
  136. package/src/components/layouts/chat.css +73 -85
  137. package/src/components/ui/Drawer.tsx +3 -1
  138. package/src/components/ui/__snapshots__/Alert.test.tsx.snap +3 -0
  139. package/src/locales/de.json +37 -1
  140. package/src/locales/en.json +37 -1
  141. package/src/locales/es.json +37 -1
  142. package/src/locales/fr.json +37 -1
  143. package/src/locales/it.json +38 -1
@@ -20,7 +20,6 @@ export interface Props {
20
20
  onLogout: () => void;
21
21
  tenant: Tenant;
22
22
  apiClient: ReturnType<typeof memoriApiClient>;
23
- baseURL?: string;
24
23
  __TEST__signup?: boolean;
25
24
  __TEST__needMissingData?: boolean;
26
25
  __TEST__waitingForOtp?: boolean;
@@ -36,7 +35,6 @@ const LoginDrawer = ({
36
35
  loginToken,
37
36
  tenant,
38
37
  apiClient,
39
- baseURL,
40
38
  __TEST__signup = false,
41
39
  __TEST__needMissingData = false,
42
40
  __TEST__waitingForOtp = false,
@@ -52,13 +50,6 @@ const LoginDrawer = ({
52
50
  const [loading, setLoading] = useState(false);
53
51
  const [error, setError] = useState<string | null>(null);
54
52
 
55
- // OTP-related state
56
- const [otpCode, setOtpCode] = useState<string>('');
57
- const [otpUserName, setOtpUserName] = useState<string>('');
58
- const [otpError, setOtpError] = useState<string | null>(null);
59
- const [showOtpForm, setShowOtpForm] = useState(false);
60
- const [otpTimer, setOtpTimer] = useState<number | null>(null);
61
-
62
53
  const [showSignup, setShowSignup] = useState(__TEST__signup);
63
54
  const [userMustChangePwd, setUserMustChangePwd] = useState<User | undefined>(
64
55
  __TEST__changePwd
@@ -85,96 +76,6 @@ const LoginDrawer = ({
85
76
  : ({} as any)
86
77
  );
87
78
 
88
- // OTP timer effect
89
- useEffect(() => {
90
- let interval: NodeJS.Timeout;
91
- if (otpTimer && otpTimer > 0) {
92
- interval = setInterval(() => {
93
- setOtpTimer(prev => (prev && prev > 0 ? prev - 1 : 0));
94
- }, 1000);
95
- }
96
- return () => {
97
- if (interval) clearInterval(interval);
98
- };
99
- }, [otpTimer]);
100
-
101
- // OTP validation function
102
- const validateOtp = async (otp: string) => {
103
- if (!otp || otp.length !== 4) {
104
- setOtpError(t('login.otpInvalidFormat'));
105
- return;
106
- }
107
-
108
- if (!otpUserName || otpUserName.trim().length === 0) {
109
- setOtpError(t('login.userNameRequired'));
110
- return;
111
- }
112
-
113
- setLoading(true);
114
- setOtpError(null);
115
-
116
- try {
117
- const response = await fetch(`${baseURL}/api/validate-otp`, {
118
- method: 'POST',
119
- headers: {
120
- 'Content-Type': 'application/json',
121
- },
122
- body: JSON.stringify({
123
- otp: otp,
124
- userName: otpUserName.trim(),
125
- }),
126
- });
127
-
128
- const data = await response.json();
129
-
130
- if (response.status == 200 && data.user && data.loginToken) {
131
- toast.success(t('login.otpSuccess'));
132
- setShowOtpForm(false);
133
- setOtpCode('');
134
- setOtpUserName('');
135
-
136
- if (!data.user?.tnCAndPPAccepted || !data.user?.birthDate) {
137
- setNeedsMissingData({
138
- token: data.loginToken,
139
- birthDate: !data.user.birthDate,
140
- tnCAndPPAccepted: !data.user.tnCAndPPAccepted,
141
- });
142
- } else {
143
- onLogin(data.user as User, data.loginToken);
144
- }
145
- } else {
146
- setOtpError(data.resultMessage || t('login.otpInvalid'));
147
- }
148
- } catch (err) {
149
- console.error('[OTP VALIDATION]', err);
150
- setOtpError(t('login.otpError'));
151
- } finally {
152
- setLoading(false);
153
- }
154
- };
155
-
156
- // Handle OTP input change
157
- const handleOtpChange = (value: string) => {
158
- const numericValue = value.replace(/\D/g, '').slice(0, 4);
159
- setOtpCode(numericValue);
160
- setOtpError(null);
161
-
162
- if (numericValue.length === 4 && otpUserName.trim().length > 0) {
163
- validateOtp(numericValue);
164
- }
165
- };
166
-
167
- // Handle username input change
168
- const handleUserNameChange = (value: string) => {
169
- setOtpUserName(value);
170
- setOtpError(null);
171
- };
172
-
173
- // Start OTP timer
174
- const startOtpTimer = () => {
175
- setOtpTimer(60);
176
- };
177
-
178
79
  const login = (e: React.FormEvent<HTMLFormElement>) => {
179
80
  e.preventDefault();
180
81
  const form = e.currentTarget as HTMLFormElement;
@@ -578,135 +479,59 @@ const LoginDrawer = ({
578
479
  <SignupForm
579
480
  tenant={tenant}
580
481
  apiClient={apiClient}
581
- onLogin={(_user: User, token: string) => {
582
- // Force all signup users to go through missing data flow
583
- // This ensures consistent user experience and data collection
584
- setNeedsMissingData({
585
- token: token,
586
- birthDate: true, // Always require birth date for new users
587
- tnCAndPPAccepted: true, // Always require terms acceptance for new users
588
- });
589
- }}
482
+ onLogin={onLogin}
590
483
  goToLogin={() => setShowSignup(false)}
591
484
  __TEST__waitingForOtp={__TEST__waitingForOtp}
592
485
  />
593
- ) : showOtpForm ? (
486
+ ) : (
594
487
  <>
595
- <div className="memori--login-drawer--otp-container">
596
- <h3>{t('login.otpTitle')}</h3>
597
- <p className="memori--login-drawer--otp-description">
598
- {t('login.otpDescription')}
599
- </p>
488
+ <form className="memori--login-drawer--form" onSubmit={login}>
489
+ <label htmlFor="#userNameOrEmail">
490
+ {t('login.userNameOrEmail')}
491
+ <input
492
+ id="userNameOrEmail"
493
+ name="userNameOrEmail"
494
+ required
495
+ autoComplete="email"
496
+ placeholder="Username/email"
497
+ />
498
+ </label>
600
499
 
601
- <div className={cx('memori--login-drawer--otp-form', { loading })}>
602
- <label htmlFor="otp-username">
603
- <span>{t('login.userName')}</span>
604
- <input
605
- id="otp-username"
606
- type="text"
607
- className="memori--login-drawer--otp-username-input"
608
- value={otpUserName}
609
- onChange={e => handleUserNameChange(e.target.value)}
610
- placeholder={t('login.userName') || 'Username'}
611
- required
612
- autoComplete="username"
613
- disabled={loading}
614
- />
615
- </label>
616
-
617
- <label htmlFor="otp-code">
618
- <span>{t('login.otpCode')}</span>
619
- <input
620
- id="otp-code"
621
- type="text"
622
- className={cx('memori--login-drawer--otp-input', {
623
- success: otpCode.length === 4 && !otpError,
624
- })}
625
- value={otpCode}
626
- onChange={e => handleOtpChange(e.target.value)}
627
- placeholder="0000"
628
- maxLength={4}
629
- autoComplete="one-time-code"
630
- required
631
- disabled={loading}
632
- />
633
- </label>
634
- </div>
635
-
636
- {otpTimer && otpTimer > 0 && (
637
- <p className="memori--login-drawer--otp-timer">
638
- {t('login.otpTimer', { seconds: otpTimer })}
639
- </p>
640
- )}
500
+ <label htmlFor="#password">
501
+ Password
502
+ <input
503
+ id="password"
504
+ name="password"
505
+ type="password"
506
+ required
507
+ autoComplete="password"
508
+ placeholder="Password"
509
+ />
510
+ </label>
641
511
 
642
- <div className="memori--login-drawer--otp-actions">
643
- <Button
644
- outlined
645
- onClick={() => {
646
- setShowOtpForm(false);
647
- setOtpCode('');
648
- setOtpUserName('');
649
- setOtpError(null);
650
- }}
651
- >
652
- {t('login.backToLogin')}
653
- </Button>
654
-
655
- <Button
656
- primary
657
- onClick={() => {
658
- window.open(
659
- 'http://localhost:3000/it/account?t=account',
660
- '_blank'
661
- );
662
- startOtpTimer();
663
- }}
664
- >
665
- {t('login.generateOtp')}
666
- </Button>
667
- </div>
668
-
669
- {otpError && (
670
- <p role="alert" className="memori--login-drawer--otp-error">
671
- {otpError}
672
- </p>
673
- )}
674
- </div>
675
- </>
676
- ) : (
677
- <>
678
- <div className="memori--login-drawer--welcome">
679
- <h3>{t('login.welcomeTitle')}</h3>
680
- <p className="memori--login-drawer--welcome-description">
681
- {t('login.welcomeDescription')}
682
- </p>
512
+ <Button htmlType="submit" primary loading={loading}>
513
+ {t('login.login')}
514
+ </Button>
683
515
 
684
- <div className="memori--login-drawer--otp-info">
685
- <div className="memori--login-drawer--otp-info-item">
686
- <span className="memori--login-drawer--otp-info-number">1</span>
687
- <span>{t('login.otpStep1')}</span>
688
- </div>
689
- <div className="memori--login-drawer--otp-info-item">
690
- <span className="memori--login-drawer--otp-info-number">2</span>
691
- <span>{t('login.otpStep2')}</span>
692
- </div>
693
- <div className="memori--login-drawer--otp-info-item">
694
- <span className="memori--login-drawer--otp-info-number">3</span>
695
- <span>{t('login.otpStep3')}</span>
516
+ {!tenant?.disableRegistration ? (
517
+ <p className="memori--login-drawer--signup">
518
+ {t('login.newUserSignUp')}{' '}
519
+ <Button outlined onClick={() => setShowSignup(true)}>
520
+ {t('login.signUp')}
521
+ </Button>
522
+ </p>
523
+ ) : tenant.adminEmail ? (
524
+ <div className="memori--login-drawer--signup">
525
+ <p>{t('login.registrationDisabled')}</p>
526
+ <p>
527
+ {t('login.contactAdmin')}:{' '}
528
+ <a href={`mailto:${tenant.adminEmail}`}>
529
+ {tenant.adminEmail}
530
+ </a>
531
+ </p>
696
532
  </div>
697
- </div>
698
-
699
- <Button
700
- primary
701
- onClick={() => {
702
- setShowOtpForm(true);
703
- startOtpTimer();
704
- }}
705
- className="memori--login-drawer--start-otp-button"
706
- >
707
- {t('login.startOtpLogin')}
708
- </Button>
709
- </div>
533
+ ) : null}
534
+ </form>
710
535
 
711
536
  {error && (
712
537
  <p role="alert" className="memori--login-drawer--error">
@@ -719,4 +544,4 @@ const LoginDrawer = ({
719
544
  );
720
545
  };
721
546
 
722
- export default LoginDrawer;
547
+ export default LoginDrawer;
@@ -91,10 +91,6 @@
91
91
  display: none;
92
92
  }
93
93
 
94
- .memori-artifact-action-text {
95
- display: none;
96
- }
97
-
98
94
  .memori-artifact-action-btn {
99
95
  min-width: 2.5rem;
100
96
  justify-content: center;
@@ -16,7 +16,7 @@ import FullscreenExit from '../../../icons/FullscreenExit';
16
16
  import PrintIcon from '../../../icons/Print';
17
17
  import { CopyButtonWithDropdown } from './';
18
18
  import { Menu, Transition } from '@headlessui/react';
19
- import MenuHorizontal from '../../../icons/MenuHorizontal';
19
+ import MenuVertical from '../../../icons/MenuVertical';
20
20
 
21
21
  const ArtifactActions: React.FC<{
22
22
  artifact: ArtifactData;
@@ -25,6 +25,7 @@ const ArtifactActions: React.FC<{
25
25
  onPrint: () => void;
26
26
  onOpenExternal: () => void;
27
27
  loading: boolean;
28
+ isMobile?: boolean;
28
29
  }> = ({
29
30
  artifact,
30
31
  onCopy,
@@ -32,6 +33,7 @@ const ArtifactActions: React.FC<{
32
33
  onPrint,
33
34
  onOpenExternal,
34
35
  loading = false,
36
+ isMobile = false,
35
37
  }) => {
36
38
  const { t } = useTranslation();
37
39
 
@@ -183,19 +185,19 @@ const ArtifactActions: React.FC<{
183
185
  loading={loading}
184
186
  className="memori-artifact-action-btn"
185
187
  />
186
- <Menu as="div" className="memori-copy-menu-wrapper">
188
+ {isMobile && <Menu as="div" className="memori-copy-menu-wrapper">
187
189
  <Menu.Button as="div" className="memori-copy-button-trigger">
188
190
  <Button
189
191
  disabled={loading}
190
192
  className={cx(
191
193
  'memori-button',
192
- 'memori-button--circle',
194
+ 'memori-button--more-options',
193
195
  'memori-button--icon-only'
194
196
  )}
195
197
  ghost
196
198
  title="More copy options"
197
199
  >
198
- <MenuHorizontal className="memori-artifact-action-icon" />
200
+ <MenuVertical className="memori-artifact-action-icon" />
199
201
  </Button>
200
202
  </Menu.Button>
201
203
 
@@ -239,7 +241,7 @@ const ArtifactActions: React.FC<{
239
241
  </div>
240
242
  </Menu.Items>
241
243
  </Transition>
242
- </Menu>
244
+ </Menu>}
243
245
  </div>
244
246
  </div>
245
247
  );
@@ -14,12 +14,16 @@ import Copy from '../../../../icons/Copy';
14
14
  import ChevronDown from '../../../../icons/ChevronDown';
15
15
  import ThumbUp from '../../../../icons/ThumbUp';
16
16
  import Alert from '../../../../icons/Alert';
17
+ import { useTranslation } from 'react-i18next';
18
+ import Link from '../../../../icons/Link';
19
+ import PrintIcon from '../../../../icons/Print';
17
20
 
18
21
  const CopyButtonWithDropdown: React.FC<CopyButtonWithDropdownProps> = ({
19
22
  artifact,
20
23
  onCopy,
21
24
  onDownload,
22
25
  onPrint,
26
+ onOpenExternal,
23
27
  loading = false,
24
28
  className,
25
29
  disabled = false,
@@ -30,6 +34,7 @@ const CopyButtonWithDropdown: React.FC<CopyButtonWithDropdownProps> = ({
30
34
  onDownload,
31
35
  onPrint
32
36
  );
37
+ const { t } = useTranslation();
33
38
 
34
39
  /**
35
40
  * Handle format selection from dropdown
@@ -38,6 +43,34 @@ const CopyButtonWithDropdown: React.FC<CopyButtonWithDropdownProps> = ({
38
43
  await handleCopy(format);
39
44
  };
40
45
 
46
+ /**
47
+ * Get MIME type string for downloads
48
+ */
49
+ const getMimeTypeString = useCallback((mimeType: string): string => {
50
+ const mimeTypes: Record<string, string> = {
51
+ html: 'text/html',
52
+ json: 'application/json',
53
+ markdown: 'text/markdown',
54
+ css: 'text/css',
55
+ javascript: 'text/javascript',
56
+ typescript: 'text/typescript',
57
+ svg: 'image/svg+xml',
58
+ xml: 'text/xml',
59
+ text: 'text/plain',
60
+ python: 'text/x-python',
61
+ java: 'text/x-java',
62
+ cpp: 'text/x-c++',
63
+ csharp: 'text/x-csharp',
64
+ php: 'text/x-php',
65
+ ruby: 'text/x-ruby',
66
+ go: 'text/x-go',
67
+ rust: 'text/x-rust',
68
+ yaml: 'text/yaml',
69
+ sql: 'text/x-sql',
70
+ };
71
+ return mimeTypes[mimeType] || 'text/plain';
72
+ }, []);
73
+
41
74
  /**
42
75
  * Get button content based on state
43
76
  */
@@ -46,7 +79,9 @@ const CopyButtonWithDropdown: React.FC<CopyButtonWithDropdownProps> = ({
46
79
  return (
47
80
  <>
48
81
  <ThumbUp className="memori-copy-button-icon memori-copy-button-icon--success" />
49
- <span className="memori-copy-button-text">Copied!</span>
82
+ <span className="memori-copy-button-text">
83
+ {t('artifact.copied') || 'Copied!'}
84
+ </span>
50
85
  </>
51
86
  );
52
87
  }
@@ -55,7 +90,9 @@ const CopyButtonWithDropdown: React.FC<CopyButtonWithDropdownProps> = ({
55
90
  return (
56
91
  <>
57
92
  <Alert className="memori-copy-button-icon memori-copy-button-icon--error" />
58
- <span className="memori-copy-button-text">Error</span>
93
+ <span className="memori-copy-button-text">
94
+ {t('artifact.error') || 'Error'}
95
+ </span>
59
96
  </>
60
97
  );
61
98
  }
@@ -68,8 +105,8 @@ const CopyButtonWithDropdown: React.FC<CopyButtonWithDropdownProps> = ({
68
105
  </div>
69
106
  <span className="memori-copy-button-text">
70
107
  {copyState.activeFormat === 'pdf'
71
- ? 'Generating PDF...'
72
- : 'Copying...'}
108
+ ? t('artifact.generatingPdf') || 'Generating PDF...'
109
+ : t('artifact.copying') || 'Copying...'}
73
110
  </span>
74
111
  </>
75
112
  );
@@ -78,11 +115,94 @@ const CopyButtonWithDropdown: React.FC<CopyButtonWithDropdownProps> = ({
78
115
  return (
79
116
  <>
80
117
  <Copy className="memori-copy-button-icon" />
81
- <span className="memori-copy-button-text">Copy</span>
118
+ <span className="memori-copy-button-text">
119
+ {t('artifact.copy') || 'Copy'}
120
+ </span>
82
121
  </>
83
122
  );
84
123
  };
85
124
 
125
+ /**
126
+ * Handle print action
127
+ */
128
+ const handlePrint = useCallback(() => {
129
+ try {
130
+ const printWindow = window.open('', '_blank');
131
+ if (!printWindow) {
132
+ alert('Popup blocked! Please enable popups to print the artifact.');
133
+ return;
134
+ }
135
+
136
+ let printContent: string;
137
+ if (artifact.mimeType === 'html') {
138
+ printContent = artifact.content;
139
+ } else {
140
+ printContent = `
141
+ <!DOCTYPE html>
142
+ <html>
143
+ <head>
144
+ <title>Artifact - ${artifact.mimeType.toUpperCase()}</title>
145
+ <style>
146
+ body {
147
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
148
+ white-space: pre-wrap;
149
+ margin: 20px;
150
+ line-height: 1.4;
151
+ }
152
+ @media print {
153
+ body { margin: 0; }
154
+ }
155
+ </style>
156
+ </head>
157
+ <body>${artifact.content
158
+ .replace(/</g, '&lt;')
159
+ .replace(/>/g, '&gt;')}</body>
160
+ </html>
161
+ `;
162
+ }
163
+
164
+ printWindow.document.write(printContent);
165
+ printWindow.document.close();
166
+
167
+ setTimeout(() => {
168
+ printWindow.print();
169
+ printWindow.close();
170
+ }, 500);
171
+
172
+ onPrint?.();
173
+ } catch (error) {
174
+ console.error('Print failed:', error);
175
+ }
176
+ }, [artifact, onPrint]);
177
+
178
+ /**
179
+ * Handle external open action
180
+ */
181
+ const handleOpenExternal = useCallback(() => {
182
+ try {
183
+ const mimeType = getMimeTypeString(artifact.mimeType);
184
+ const blob = new Blob([artifact.content], { type: mimeType });
185
+ const url = URL.createObjectURL(blob);
186
+
187
+ const externalWindow = window.open(url, '_blank');
188
+ if (!externalWindow) {
189
+ alert(
190
+ 'Popup blocked! Please enable popups to open the artifact in a new window.'
191
+ );
192
+ return;
193
+ }
194
+
195
+ // Cleanup URL after a delay
196
+ setTimeout(() => {
197
+ URL.revokeObjectURL(url);
198
+ }, 60000);
199
+
200
+ onOpenExternal?.();
201
+ } catch (error) {
202
+ console.error('External open failed:', error);
203
+ }
204
+ }, [artifact, getMimeTypeString, onOpenExternal]);
205
+
86
206
  /**
87
207
  * Get button title/tooltip
88
208
  */
@@ -157,12 +277,6 @@ const CopyButtonWithDropdown: React.FC<CopyButtonWithDropdownProps> = ({
157
277
  >
158
278
  <Menu.Items className="memori-copy-dropdown">
159
279
  <div className="memori-copy-dropdown-content">
160
- <div className="memori-copy-dropdown-header">
161
- <span className="memori-copy-dropdown-title">
162
- Copy as
163
- </span>
164
- </div>
165
-
166
280
  <div className="memori-copy-dropdown-list">
167
281
  {formats.map(format => (
168
282
  <Menu.Item key={format.id}>
@@ -176,6 +290,29 @@ const CopyButtonWithDropdown: React.FC<CopyButtonWithDropdownProps> = ({
176
290
  )}
177
291
  </Menu.Item>
178
292
  ))}
293
+ <CopyMenuItem
294
+ format={{
295
+ id: 'external',
296
+ label: t('artifact.external') || 'External',
297
+ action: 'link',
298
+ mimeType: artifact.mimeType,
299
+ }}
300
+ onClick={handleOpenExternal}
301
+ loading={copyState.loading}
302
+ active={false}
303
+ />
304
+ <CopyMenuItem
305
+ format={{
306
+ id: 'print',
307
+ label: t('artifact.print') || 'Print',
308
+ action: 'print',
309
+ mimeType: artifact.mimeType,
310
+ }}
311
+ onClick={handlePrint}
312
+ loading={copyState.loading}
313
+ active={false}
314
+ />
315
+
179
316
  </div>
180
317
  </div>
181
318
  </Menu.Items>
@@ -185,7 +322,6 @@ const CopyButtonWithDropdown: React.FC<CopyButtonWithDropdownProps> = ({
185
322
  </Menu>
186
323
  )}
187
324
  </div>
188
-
189
325
  </div>
190
326
  );
191
327
  };
@@ -9,6 +9,7 @@ import { CopyMenuItemProps } from '../types';
9
9
  import Copy from '../../../../icons/Copy';
10
10
  import Download from '../../../../icons/Download';
11
11
  import PrintIcon from '../../../../icons/Print';
12
+ import Link from '../../../../icons/Link';
12
13
 
13
14
  const CopyMenuItem: React.FC<CopyMenuItemProps & { active?: boolean }> = ({
14
15
  format,
@@ -37,6 +38,8 @@ const CopyMenuItem: React.FC<CopyMenuItemProps & { active?: boolean }> = ({
37
38
  return <Download className="memori-copy-menu-item-icon" />;
38
39
  case 'print':
39
40
  return <PrintIcon className="memori-copy-menu-item-icon" />;
41
+ case 'link':
42
+ return <Link className="memori-copy-menu-item-icon" />;
40
43
  default:
41
44
  return <Copy className="memori-copy-menu-item-icon" />;
42
45
  }