@oxy.so/bloom 4.19.0 → 4.20.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.
- package/docs/adoption-matrix.mdx +3 -3
- package/docs/ai-chat.mdx +32 -1
- package/docs/composer-panel.mdx +4 -0
- package/lib/commonjs/ai-chat/AiChat.js +2 -1
- package/lib/commonjs/ai-chat/AiChat.js.map +1 -1
- package/lib/commonjs/ai-chat/AiChat.web.js +2 -1
- package/lib/commonjs/ai-chat/AiChat.web.js.map +1 -1
- package/lib/commonjs/ai-chat/AiChatControls.js +60 -4
- package/lib/commonjs/ai-chat/AiChatControls.js.map +1 -1
- package/lib/commonjs/ai-chat/AiChatFeedbackRowBase.js +40 -12
- package/lib/commonjs/ai-chat/AiChatFeedbackRowBase.js.map +1 -1
- package/lib/commonjs/ai-chat/AiChatMessages.js +31 -5
- package/lib/commonjs/ai-chat/AiChatMessages.js.map +1 -1
- package/lib/commonjs/ai-chat/bindAiChat.js +10 -0
- package/lib/commonjs/ai-chat/bindAiChat.js.map +1 -1
- package/lib/commonjs/ai-chat/index.js +1 -1
- package/lib/commonjs/ai-chat/index.js.map +1 -1
- package/lib/commonjs/ai-chat/index.web.js +1 -1
- package/lib/commonjs/ai-chat/index.web.js.map +1 -1
- package/lib/commonjs/ai-chat/shared.js +11 -2
- package/lib/commonjs/ai-chat/shared.js.map +1 -1
- package/lib/commonjs/composer-panel/AttachmentStrip.js +108 -8
- package/lib/commonjs/composer-panel/AttachmentStrip.js.map +1 -1
- package/lib/commonjs/composer-panel/ComposerPanelBase.js +6 -2
- package/lib/commonjs/composer-panel/ComposerPanelBase.js.map +1 -1
- package/lib/commonjs/composer-panel/ComposerPillBase.js +2 -1
- package/lib/commonjs/composer-panel/ComposerPillBase.js.map +1 -1
- package/lib/commonjs/composer-panel/shared.js +2 -0
- package/lib/commonjs/composer-panel/shared.js.map +1 -1
- package/lib/commonjs/composer-panel/use-attachment-queue.js +5 -2
- package/lib/commonjs/composer-panel/use-attachment-queue.js.map +1 -1
- package/lib/commonjs/input-otp/InputOtp.js +13 -3
- package/lib/commonjs/input-otp/InputOtp.js.map +1 -1
- package/lib/module/ai-chat/AiChat.js +1 -0
- package/lib/module/ai-chat/AiChat.js.map +1 -1
- package/lib/module/ai-chat/AiChat.web.js +1 -0
- package/lib/module/ai-chat/AiChat.web.js.map +1 -1
- package/lib/module/ai-chat/AiChatControls.js +58 -4
- package/lib/module/ai-chat/AiChatControls.js.map +1 -1
- package/lib/module/ai-chat/AiChatFeedbackRowBase.js +41 -13
- package/lib/module/ai-chat/AiChatFeedbackRowBase.js.map +1 -1
- package/lib/module/ai-chat/AiChatMessages.js +30 -4
- package/lib/module/ai-chat/AiChatMessages.js.map +1 -1
- package/lib/module/ai-chat/bindAiChat.js +11 -1
- package/lib/module/ai-chat/bindAiChat.js.map +1 -1
- package/lib/module/ai-chat/index.js +2 -2
- package/lib/module/ai-chat/index.js.map +1 -1
- package/lib/module/ai-chat/index.web.js +2 -2
- package/lib/module/ai-chat/index.web.js.map +1 -1
- package/lib/module/ai-chat/shared.js +11 -2
- package/lib/module/ai-chat/shared.js.map +1 -1
- package/lib/module/composer-panel/AttachmentStrip.js +108 -8
- package/lib/module/composer-panel/AttachmentStrip.js.map +1 -1
- package/lib/module/composer-panel/ComposerPanelBase.js +6 -2
- package/lib/module/composer-panel/ComposerPanelBase.js.map +1 -1
- package/lib/module/composer-panel/ComposerPillBase.js +2 -1
- package/lib/module/composer-panel/ComposerPillBase.js.map +1 -1
- package/lib/module/composer-panel/shared.js +2 -0
- package/lib/module/composer-panel/shared.js.map +1 -1
- package/lib/module/composer-panel/use-attachment-queue.js +5 -2
- package/lib/module/composer-panel/use-attachment-queue.js.map +1 -1
- package/lib/module/input-otp/InputOtp.js +13 -3
- package/lib/module/input-otp/InputOtp.js.map +1 -1
- package/lib/typescript/commonjs/ai-chat/AiChat.d.ts +4 -0
- package/lib/typescript/commonjs/ai-chat/AiChat.d.ts.map +1 -1
- package/lib/typescript/commonjs/ai-chat/AiChat.web.d.ts +4 -0
- package/lib/typescript/commonjs/ai-chat/AiChat.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/ai-chat/AiChatControls.d.ts +22 -1
- package/lib/typescript/commonjs/ai-chat/AiChatControls.d.ts.map +1 -1
- package/lib/typescript/commonjs/ai-chat/AiChatFeedbackRowBase.d.ts +5 -2
- package/lib/typescript/commonjs/ai-chat/AiChatFeedbackRowBase.d.ts.map +1 -1
- package/lib/typescript/commonjs/ai-chat/AiChatMessages.d.ts +1 -1
- package/lib/typescript/commonjs/ai-chat/AiChatMessages.d.ts.map +1 -1
- package/lib/typescript/commonjs/ai-chat/bindAiChat.d.ts +5 -1
- package/lib/typescript/commonjs/ai-chat/bindAiChat.d.ts.map +1 -1
- package/lib/typescript/commonjs/ai-chat/index.d.ts +3 -3
- package/lib/typescript/commonjs/ai-chat/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/ai-chat/index.web.d.ts +3 -3
- package/lib/typescript/commonjs/ai-chat/index.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/ai-chat/shared.d.ts +2 -2
- package/lib/typescript/commonjs/ai-chat/shared.d.ts.map +1 -1
- package/lib/typescript/commonjs/ai-chat/types.d.ts +43 -5
- package/lib/typescript/commonjs/ai-chat/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/composer-panel/AttachmentStrip.d.ts +10 -1
- package/lib/typescript/commonjs/composer-panel/AttachmentStrip.d.ts.map +1 -1
- package/lib/typescript/commonjs/composer-panel/ComposerPanelBase.d.ts +1 -1
- package/lib/typescript/commonjs/composer-panel/ComposerPanelBase.d.ts.map +1 -1
- package/lib/typescript/commonjs/composer-panel/shared.d.ts +3 -0
- package/lib/typescript/commonjs/composer-panel/shared.d.ts.map +1 -1
- package/lib/typescript/commonjs/composer-panel/types.d.ts +11 -0
- package/lib/typescript/commonjs/composer-panel/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/composer-panel/use-attachment-queue.d.ts.map +1 -1
- package/lib/typescript/commonjs/input-otp/InputOtp.d.ts.map +1 -1
- package/lib/typescript/module/ai-chat/AiChat.d.ts +4 -0
- package/lib/typescript/module/ai-chat/AiChat.d.ts.map +1 -1
- package/lib/typescript/module/ai-chat/AiChat.web.d.ts +4 -0
- package/lib/typescript/module/ai-chat/AiChat.web.d.ts.map +1 -1
- package/lib/typescript/module/ai-chat/AiChatControls.d.ts +22 -1
- package/lib/typescript/module/ai-chat/AiChatControls.d.ts.map +1 -1
- package/lib/typescript/module/ai-chat/AiChatFeedbackRowBase.d.ts +5 -2
- package/lib/typescript/module/ai-chat/AiChatFeedbackRowBase.d.ts.map +1 -1
- package/lib/typescript/module/ai-chat/AiChatMessages.d.ts +1 -1
- package/lib/typescript/module/ai-chat/AiChatMessages.d.ts.map +1 -1
- package/lib/typescript/module/ai-chat/bindAiChat.d.ts +5 -1
- package/lib/typescript/module/ai-chat/bindAiChat.d.ts.map +1 -1
- package/lib/typescript/module/ai-chat/index.d.ts +3 -3
- package/lib/typescript/module/ai-chat/index.d.ts.map +1 -1
- package/lib/typescript/module/ai-chat/index.web.d.ts +3 -3
- package/lib/typescript/module/ai-chat/index.web.d.ts.map +1 -1
- package/lib/typescript/module/ai-chat/shared.d.ts +2 -2
- package/lib/typescript/module/ai-chat/shared.d.ts.map +1 -1
- package/lib/typescript/module/ai-chat/types.d.ts +43 -5
- package/lib/typescript/module/ai-chat/types.d.ts.map +1 -1
- package/lib/typescript/module/composer-panel/AttachmentStrip.d.ts +10 -1
- package/lib/typescript/module/composer-panel/AttachmentStrip.d.ts.map +1 -1
- package/lib/typescript/module/composer-panel/ComposerPanelBase.d.ts +1 -1
- package/lib/typescript/module/composer-panel/ComposerPanelBase.d.ts.map +1 -1
- package/lib/typescript/module/composer-panel/shared.d.ts +3 -0
- package/lib/typescript/module/composer-panel/shared.d.ts.map +1 -1
- package/lib/typescript/module/composer-panel/types.d.ts +11 -0
- package/lib/typescript/module/composer-panel/types.d.ts.map +1 -1
- package/lib/typescript/module/composer-panel/use-attachment-queue.d.ts.map +1 -1
- package/lib/typescript/module/input-otp/InputOtp.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/ai-chat/AiChat.tsx +1 -0
- package/src/ai-chat/AiChat.web.tsx +1 -0
- package/src/ai-chat/AiChatControls.tsx +58 -3
- package/src/ai-chat/AiChatFeedbackRowBase.tsx +45 -17
- package/src/ai-chat/AiChatMessages.tsx +24 -1
- package/src/ai-chat/bindAiChat.tsx +19 -2
- package/src/ai-chat/index.ts +3 -1
- package/src/ai-chat/index.web.ts +3 -1
- package/src/ai-chat/shared.ts +11 -2
- package/src/ai-chat/types.ts +45 -5
- package/src/composer-panel/AttachmentStrip.tsx +98 -6
- package/src/composer-panel/ComposerPanelBase.tsx +4 -0
- package/src/composer-panel/ComposerPillBase.tsx +1 -0
- package/src/composer-panel/shared.ts +5 -0
- package/src/composer-panel/types.ts +11 -0
- package/src/composer-panel/use-attachment-queue.ts +5 -2
- package/src/input-otp/InputOtp.tsx +11 -2
|
@@ -4,7 +4,7 @@ import { AiChatContainerBase } from './AiChatContainer';
|
|
|
4
4
|
import { AiChatFeedbackRowBase } from './AiChatFeedbackRowBase';
|
|
5
5
|
import { AiChatGalleryPanelBase } from './AiChatGalleryPanelBase';
|
|
6
6
|
import { AiChatImageGenerationBase } from './AiChatImageGenerationBase';
|
|
7
|
-
import { AiChatAssistantMessageBase } from './AiChatMessages';
|
|
7
|
+
import { AiChatAssistantMessageBase, AiChatUserMessageBase } from './AiChatMessages';
|
|
8
8
|
import { AiChatPlatformContext, type AiChatPlatform } from './context';
|
|
9
9
|
import type {
|
|
10
10
|
AiChatAssistantMessageProps,
|
|
@@ -12,6 +12,7 @@ import type {
|
|
|
12
12
|
AiChatFeedbackRowProps,
|
|
13
13
|
AiChatGalleryPanelProps,
|
|
14
14
|
AiChatImageGenerationProps,
|
|
15
|
+
AiChatUserMessageProps,
|
|
15
16
|
} from './types';
|
|
16
17
|
|
|
17
18
|
/**
|
|
@@ -29,6 +30,15 @@ export function bindAiChat(platform: AiChatPlatform) {
|
|
|
29
30
|
}
|
|
30
31
|
AiChatFeedbackRow.displayName = 'AiChatFeedbackRow';
|
|
31
32
|
|
|
33
|
+
function AiChatUserMessage(props: AiChatUserMessageProps) {
|
|
34
|
+
return (
|
|
35
|
+
<AiChatPlatformContext.Provider value={platform}>
|
|
36
|
+
<AiChatUserMessageBase {...props} />
|
|
37
|
+
</AiChatPlatformContext.Provider>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
AiChatUserMessage.displayName = 'AiChatUserMessage';
|
|
41
|
+
|
|
32
42
|
function AiChatAssistantMessage(props: AiChatAssistantMessageProps) {
|
|
33
43
|
return (
|
|
34
44
|
<AiChatPlatformContext.Provider value={platform}>
|
|
@@ -65,5 +75,12 @@ export function bindAiChat(platform: AiChatPlatform) {
|
|
|
65
75
|
}
|
|
66
76
|
AiChatContainer.displayName = 'AiChatContainer';
|
|
67
77
|
|
|
68
|
-
return {
|
|
78
|
+
return {
|
|
79
|
+
AiChatFeedbackRow,
|
|
80
|
+
AiChatUserMessage,
|
|
81
|
+
AiChatAssistantMessage,
|
|
82
|
+
AiChatImageGeneration,
|
|
83
|
+
AiChatGalleryPanel,
|
|
84
|
+
AiChatContainer,
|
|
85
|
+
};
|
|
69
86
|
}
|
package/src/ai-chat/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ export {
|
|
|
4
4
|
AiChatFeedbackRow,
|
|
5
5
|
AiChatGalleryPanel,
|
|
6
6
|
AiChatImageGeneration,
|
|
7
|
+
AiChatUserMessage,
|
|
7
8
|
} from './AiChat';
|
|
8
9
|
export { AiChatCodePanel } from './AiChatCodePanel';
|
|
9
10
|
export { AiChatThread } from './AiChatContainer';
|
|
@@ -13,7 +14,6 @@ export {
|
|
|
13
14
|
AiChatLinkChip,
|
|
14
15
|
AiChatMessageLine,
|
|
15
16
|
AiChatStrong,
|
|
16
|
-
AiChatUserMessage,
|
|
17
17
|
} from './AiChatMessages';
|
|
18
18
|
export { AiChatMobileHeader, AiChatResizeHandle, AiChatShell } from './AiChatShell';
|
|
19
19
|
export { useAiChatChromeInsets, useAiChatShell } from './context';
|
|
@@ -27,6 +27,7 @@ export type {
|
|
|
27
27
|
AiChatCodePanelProps,
|
|
28
28
|
AiChatContainerLabels,
|
|
29
29
|
AiChatContainerProps,
|
|
30
|
+
AiChatCopyResult,
|
|
30
31
|
AiChatFeedbackLabels,
|
|
31
32
|
AiChatFeedbackRowProps,
|
|
32
33
|
AiChatGalleryPanelLabels,
|
|
@@ -46,5 +47,6 @@ export type {
|
|
|
46
47
|
AiChatStrongProps,
|
|
47
48
|
AiChatThreadHandle,
|
|
48
49
|
AiChatThreadProps,
|
|
50
|
+
AiChatTurnAction,
|
|
49
51
|
AiChatUserMessageProps,
|
|
50
52
|
} from './types';
|
package/src/ai-chat/index.web.ts
CHANGED
|
@@ -9,6 +9,7 @@ export {
|
|
|
9
9
|
AiChatFeedbackRow,
|
|
10
10
|
AiChatGalleryPanel,
|
|
11
11
|
AiChatImageGeneration,
|
|
12
|
+
AiChatUserMessage,
|
|
12
13
|
} from './AiChat.web';
|
|
13
14
|
export { AiChatCodePanel } from './AiChatCodePanel';
|
|
14
15
|
export { AiChatThread } from './AiChatContainer';
|
|
@@ -18,7 +19,6 @@ export {
|
|
|
18
19
|
AiChatLinkChip,
|
|
19
20
|
AiChatMessageLine,
|
|
20
21
|
AiChatStrong,
|
|
21
|
-
AiChatUserMessage,
|
|
22
22
|
} from './AiChatMessages';
|
|
23
23
|
export { AiChatMobileHeader, AiChatResizeHandle, AiChatShell } from './AiChatShell';
|
|
24
24
|
export { useAiChatChromeInsets, useAiChatShell } from './context';
|
|
@@ -32,6 +32,7 @@ export type {
|
|
|
32
32
|
AiChatCodePanelProps,
|
|
33
33
|
AiChatContainerLabels,
|
|
34
34
|
AiChatContainerProps,
|
|
35
|
+
AiChatCopyResult,
|
|
35
36
|
AiChatFeedbackLabels,
|
|
36
37
|
AiChatFeedbackRowProps,
|
|
37
38
|
AiChatGalleryPanelLabels,
|
|
@@ -51,5 +52,6 @@ export type {
|
|
|
51
52
|
AiChatStrongProps,
|
|
52
53
|
AiChatThreadHandle,
|
|
53
54
|
AiChatThreadProps,
|
|
55
|
+
AiChatTurnAction,
|
|
54
56
|
AiChatUserMessageProps,
|
|
55
57
|
} from './types';
|
package/src/ai-chat/shared.ts
CHANGED
|
@@ -176,7 +176,7 @@ const STYLE_ID = 'bloom-ai-chat-web-css';
|
|
|
176
176
|
* - the split-flap countdown's two leaves (`flap-fall` / `flap-rise`);
|
|
177
177
|
* - the generated image's feathered radial reveal, a registered `--bloom-ai-reveal`
|
|
178
178
|
* percentage transitioning 0% → 132% over 1550ms;
|
|
179
|
-
* - the resize grip's hover reveal;
|
|
179
|
+
* - the resize grip's hover reveal, and a user turn's actions (pointer only);
|
|
180
180
|
* - the composer field's placeholder colour;
|
|
181
181
|
* - a document-scrolled thread out of the browser's scroll anchoring.
|
|
182
182
|
*/
|
|
@@ -260,6 +260,14 @@ export const AI_CHAT_WEB_CSS = `
|
|
|
260
260
|
[data-bloom-ai-chat-grip-pill="dragging"] { opacity: 1; }
|
|
261
261
|
[data-bloom-ai-chat-grip] { cursor: col-resize; touch-action: none; outline: none; }
|
|
262
262
|
[data-bloom-ai-chat-dragging="on"] { cursor: col-resize; user-select: none; }
|
|
263
|
+
@media (hover: hover) {
|
|
264
|
+
[data-bloom-ai-chat-turn] [data-bloom-ai-chat-turn-actions] {
|
|
265
|
+
opacity: 0;
|
|
266
|
+
transition: opacity ${TRANSITION_MS}ms ease;
|
|
267
|
+
}
|
|
268
|
+
[data-bloom-ai-chat-turn]:hover [data-bloom-ai-chat-turn-actions],
|
|
269
|
+
[data-bloom-ai-chat-turn]:focus-within [data-bloom-ai-chat-turn-actions] { opacity: 1; }
|
|
270
|
+
}
|
|
263
271
|
[data-bloom-ai-chat-chip] {
|
|
264
272
|
display: inline-flex;
|
|
265
273
|
max-width: calc(100% - 4px);
|
|
@@ -275,7 +283,8 @@ export const AI_CHAT_WEB_CSS = `
|
|
|
275
283
|
[data-bloom-ai-chat-control],
|
|
276
284
|
[data-bloom-ai-chat-tile] [data-bloom-ai-chat-scrim],
|
|
277
285
|
[data-bloom-ai-chat-tile-image],
|
|
278
|
-
[data-bloom-ai-chat-grip] [data-bloom-ai-chat-grip-pill]
|
|
286
|
+
[data-bloom-ai-chat-grip] [data-bloom-ai-chat-grip-pill],
|
|
287
|
+
[data-bloom-ai-chat-turn] [data-bloom-ai-chat-turn-actions] { transition: none; }
|
|
279
288
|
[data-bloom-ai-chat-flap] { animation-duration: 1ms; animation-delay: 0ms; }
|
|
280
289
|
}
|
|
281
290
|
`;
|
package/src/ai-chat/types.ts
CHANGED
|
@@ -30,14 +30,47 @@ export interface AiChatFeedbackLabels {
|
|
|
30
30
|
copied?: string;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* What a copy handler reports. `void` (or `true`) is a copy that happened;
|
|
35
|
+
* `false` is one that did not. A promise is waited for: the confirmation shows
|
|
36
|
+
* once it resolves, and never if it resolves `false` or rejects.
|
|
37
|
+
*/
|
|
38
|
+
export type AiChatCopyResult = void | boolean | Promise<void | boolean>;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* One extra action on a turn — read aloud, regenerate, edit. Drawn with the
|
|
42
|
+
* feedback row's own button: a 16px glyph on a 28px square, a tooltip naming it.
|
|
43
|
+
*/
|
|
44
|
+
export interface AiChatTurnAction {
|
|
45
|
+
/** Stable React key, and the suffix of the button's `testID`. */
|
|
46
|
+
key: string;
|
|
47
|
+
/** The accessible name, and the tooltip's copy. */
|
|
48
|
+
label: string;
|
|
49
|
+
/** A Bloom icon component (`RiVolumeUpLine`, not `<RiVolumeUpLine />`). */
|
|
50
|
+
icon: AiChatIconComponent;
|
|
51
|
+
onPress: () => void;
|
|
52
|
+
/**
|
|
53
|
+
* Makes the action a TOGGLE (read aloud while it is speaking): `true` paints
|
|
54
|
+
* the hover surface and the primary glyph, and announces it pressed. Leave it
|
|
55
|
+
* undefined for a plain button — `false` announces "not pressed".
|
|
56
|
+
*/
|
|
57
|
+
active?: boolean;
|
|
58
|
+
/** Dimmed to the shared disabled opacity, unfocusable, announced disabled. */
|
|
59
|
+
disabled?: boolean;
|
|
60
|
+
}
|
|
61
|
+
|
|
33
62
|
export interface AiChatFeedbackRowProps {
|
|
34
63
|
onLike?: () => void;
|
|
35
64
|
onDislike?: () => void;
|
|
36
65
|
/**
|
|
37
|
-
* Copy handler. The glyph swaps to a check and the tooltip reads "Copied!"
|
|
38
|
-
* 1.6s
|
|
66
|
+
* Copy handler. The glyph swaps to a check and the tooltip reads "Copied!"
|
|
67
|
+
* for 1.6s once the copy has happened: at once for a handler that returns
|
|
68
|
+
* nothing (or `true`), after the promise resolves for an async one, and not
|
|
69
|
+
* at all when it returns or resolves `false`, throws or rejects.
|
|
39
70
|
*/
|
|
40
|
-
onCopy?: () =>
|
|
71
|
+
onCopy?: () => AiChatCopyResult;
|
|
72
|
+
/** More buttons after copy, in order — read aloud, regenerate… */
|
|
73
|
+
actions?: ReadonlyArray<AiChatTurnAction>;
|
|
41
74
|
labels?: AiChatFeedbackLabels;
|
|
42
75
|
style?: StyleProp<ViewStyle>;
|
|
43
76
|
testID?: string;
|
|
@@ -50,6 +83,13 @@ export interface AiChatFeedbackRowProps {
|
|
|
50
83
|
export interface AiChatUserMessageProps {
|
|
51
84
|
/** `AiChatMessageLine`s — or a string, wrapped in one. */
|
|
52
85
|
children: ReactNode;
|
|
86
|
+
/**
|
|
87
|
+
* Buttons under the card, right-aligned — copy, edit. On a web pointer they
|
|
88
|
+
* fade in while the turn is hovered or holds focus; they stay in the tab order
|
|
89
|
+
* and the accessibility tree throughout, and on native (and touch web) they
|
|
90
|
+
* are always shown.
|
|
91
|
+
*/
|
|
92
|
+
actions?: ReadonlyArray<AiChatTurnAction>;
|
|
53
93
|
/** Skip the blur-in (a turn restored from history). Default `true`. */
|
|
54
94
|
animate?: boolean;
|
|
55
95
|
style?: StyleProp<ViewStyle>;
|
|
@@ -64,7 +104,7 @@ export interface AiChatAssistantMessageProps {
|
|
|
64
104
|
children: ReactNode;
|
|
65
105
|
/** The like / dislike / copy row under the reply. Default `true`. */
|
|
66
106
|
feedback?: boolean;
|
|
67
|
-
/** Feedback handlers and labels. */
|
|
107
|
+
/** Feedback handlers, extra `actions` and labels. */
|
|
68
108
|
feedbackProps?: Omit<AiChatFeedbackRowProps, 'style' | 'testID'>;
|
|
69
109
|
/** Skip the staggered blur-in. Default `true`. */
|
|
70
110
|
animate?: boolean;
|
|
@@ -141,7 +181,7 @@ export interface AiChatImageGenerationProps {
|
|
|
141
181
|
/** Feedback handlers under the landed image. */
|
|
142
182
|
onLike?: () => void;
|
|
143
183
|
onDislike?: () => void;
|
|
144
|
-
onCopy?: () =>
|
|
184
|
+
onCopy?: () => AiChatCopyResult;
|
|
145
185
|
labels?: AiChatImageGenerationLabels;
|
|
146
186
|
feedbackLabels?: AiChatFeedbackLabels;
|
|
147
187
|
style?: StyleProp<ViewStyle>;
|
|
@@ -10,6 +10,8 @@ import Animated, {
|
|
|
10
10
|
import Svg, { Path } from 'react-native-svg';
|
|
11
11
|
|
|
12
12
|
import { mixColor } from '../button/shared';
|
|
13
|
+
import { RiErrorWarningLine } from '../icons/remix/RiErrorWarningLine';
|
|
14
|
+
import { RiRefreshLine } from '../icons/remix/RiRefreshLine';
|
|
13
15
|
import { Text } from '../typography';
|
|
14
16
|
import type { WebCssStyle } from '../styles/web-view-style';
|
|
15
17
|
import {
|
|
@@ -101,6 +103,41 @@ function TileDismiss({
|
|
|
101
103
|
);
|
|
102
104
|
}
|
|
103
105
|
|
|
106
|
+
/**
|
|
107
|
+
* A failed tile's retry: a 24px disc on the card surface where the kind glyph
|
|
108
|
+
* sits (3, 4), a 14px refresh glyph in the error foreground, text-primary on
|
|
109
|
+
* hover.
|
|
110
|
+
*/
|
|
111
|
+
function TileRetry({ label, onPress, palette }: { label: string; onPress: () => void; palette: ComposerPalette }) {
|
|
112
|
+
const [hovered, setHovered] = useState(false);
|
|
113
|
+
const style: WebCssStyle = {
|
|
114
|
+
position: 'absolute',
|
|
115
|
+
top: 3,
|
|
116
|
+
left: 3,
|
|
117
|
+
width: 24,
|
|
118
|
+
height: 24,
|
|
119
|
+
borderRadius: 12,
|
|
120
|
+
alignItems: 'center',
|
|
121
|
+
justifyContent: 'center',
|
|
122
|
+
backgroundColor: palette.surface,
|
|
123
|
+
cursor: 'pointer',
|
|
124
|
+
'--bloom-composer-ring': palette.focusRing,
|
|
125
|
+
};
|
|
126
|
+
return (
|
|
127
|
+
<Pressable
|
|
128
|
+
{...dataHook('bloomComposerControl', 'offset')}
|
|
129
|
+
accessibilityRole="button"
|
|
130
|
+
accessibilityLabel={label}
|
|
131
|
+
onPress={onPress}
|
|
132
|
+
onHoverIn={() => setHovered(true)}
|
|
133
|
+
onHoverOut={() => setHovered(false)}
|
|
134
|
+
hitSlop={4}
|
|
135
|
+
style={style}>
|
|
136
|
+
<RiRefreshLine width={14} height={14} fill={hovered ? palette.text : palette.errorText} />
|
|
137
|
+
</Pressable>
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
104
141
|
/** The upload percentage's box: top 3 / right 3, blurring out as it fades. */
|
|
105
142
|
function percentStyle(inFlight: boolean): WebCssStyle {
|
|
106
143
|
return {
|
|
@@ -123,7 +160,9 @@ interface TileProps {
|
|
|
123
160
|
attachment: ComposerPanelAttachment;
|
|
124
161
|
palette: ComposerPalette;
|
|
125
162
|
onRemove?: () => void;
|
|
163
|
+
onRetry?: () => void;
|
|
126
164
|
removeLabel: string;
|
|
165
|
+
retryLabel: string;
|
|
127
166
|
}
|
|
128
167
|
|
|
129
168
|
/**
|
|
@@ -134,9 +173,23 @@ interface TileProps {
|
|
|
134
173
|
* percentage (9/16 medium, tabular; white over a photo, accent-500 otherwise)
|
|
135
174
|
* sits at top 3 / right 3; once landed it blurs out as the dismiss blurs in on
|
|
136
175
|
* the same spot.
|
|
176
|
+
*
|
|
177
|
+
* A FAILED tile (`error`) drops the ring and percentage and fills with the
|
|
178
|
+
* error subtle surface under an error-foreground border: the warning glyph (or
|
|
179
|
+
* the retry button) at (3, 4) and the message in 9/11 medium, two lines at
|
|
180
|
+
* most, from (5, 30). The dismiss shows as on a landed tile.
|
|
137
181
|
*/
|
|
138
|
-
export const AttachmentTile = memo(function AttachmentTile({
|
|
139
|
-
|
|
182
|
+
export const AttachmentTile = memo(function AttachmentTile({
|
|
183
|
+
attachment,
|
|
184
|
+
palette,
|
|
185
|
+
onRemove,
|
|
186
|
+
onRetry,
|
|
187
|
+
removeLabel,
|
|
188
|
+
retryLabel,
|
|
189
|
+
}: TileProps) {
|
|
190
|
+
const { error } = attachment;
|
|
191
|
+
const failed = !!error;
|
|
192
|
+
const progress = failed ? undefined : attachment.progress;
|
|
140
193
|
const inFlight = progress !== undefined;
|
|
141
194
|
const image = attachment.kind === 'image' ? attachment.src : undefined;
|
|
142
195
|
const dash = inFlight && progress < 100 ? (Math.max(0, progress) / 100) * TILE_RING_LENGTH : TILE_RING_LENGTH + 2;
|
|
@@ -144,8 +197,8 @@ export const AttachmentTile = memo(function AttachmentTile({ attachment, palette
|
|
|
144
197
|
|
|
145
198
|
return (
|
|
146
199
|
<View
|
|
147
|
-
accessibilityLabel={attachment.name}
|
|
148
|
-
{...(IS_WEB ? { title: attachment.name } : {})}
|
|
200
|
+
accessibilityLabel={failed ? `${attachment.name}: ${error}` : attachment.name}
|
|
201
|
+
{...(IS_WEB ? { title: failed ? `${attachment.name}: ${error}` : attachment.name } : {})}
|
|
149
202
|
style={{
|
|
150
203
|
position: 'relative',
|
|
151
204
|
width: TILE,
|
|
@@ -153,7 +206,7 @@ export const AttachmentTile = memo(function AttachmentTile({ attachment, palette
|
|
|
153
206
|
flexShrink: 0,
|
|
154
207
|
borderRadius: TILE_RADIUS,
|
|
155
208
|
borderWidth: 1,
|
|
156
|
-
borderColor: palette.tileBorder,
|
|
209
|
+
borderColor: failed ? palette.errorText : palette.tileBorder,
|
|
157
210
|
}}>
|
|
158
211
|
{image ? (
|
|
159
212
|
<View
|
|
@@ -196,6 +249,41 @@ export const AttachmentTile = memo(function AttachmentTile({ attachment, palette
|
|
|
196
249
|
</View>
|
|
197
250
|
)}
|
|
198
251
|
|
|
252
|
+
{failed ? (
|
|
253
|
+
<View
|
|
254
|
+
style={{
|
|
255
|
+
position: 'absolute',
|
|
256
|
+
top: 0,
|
|
257
|
+
left: 0,
|
|
258
|
+
right: 0,
|
|
259
|
+
bottom: 0,
|
|
260
|
+
borderRadius: TILE_RADIUS - 1,
|
|
261
|
+
backgroundColor: palette.errorSurface,
|
|
262
|
+
}}>
|
|
263
|
+
{onRetry ? null : (
|
|
264
|
+
<View style={{ position: 'absolute', top: 4, left: 3, width: 24, height: 24, alignItems: 'center', justifyContent: 'center' }}>
|
|
265
|
+
<RiErrorWarningLine width={20} height={20} fill={palette.errorText} />
|
|
266
|
+
</View>
|
|
267
|
+
)}
|
|
268
|
+
<Text
|
|
269
|
+
numberOfLines={2}
|
|
270
|
+
style={{
|
|
271
|
+
position: 'absolute',
|
|
272
|
+
top: 30,
|
|
273
|
+
left: 5,
|
|
274
|
+
right: 5,
|
|
275
|
+
// 9px has no type-ramp step; spelled out literally.
|
|
276
|
+
fontSize: 9,
|
|
277
|
+
lineHeight: 11,
|
|
278
|
+
fontWeight: '500',
|
|
279
|
+
color: palette.errorText,
|
|
280
|
+
}}>
|
|
281
|
+
{error}
|
|
282
|
+
</Text>
|
|
283
|
+
{onRetry ? <TileRetry label={`${retryLabel} ${attachment.name}`} onPress={onRetry} palette={palette} /> : null}
|
|
284
|
+
</View>
|
|
285
|
+
) : null}
|
|
286
|
+
|
|
199
287
|
{/* Upload ring over the border: the svg spans the border box. */}
|
|
200
288
|
<View
|
|
201
289
|
pointerEvents="none"
|
|
@@ -288,7 +376,9 @@ interface StripProps {
|
|
|
288
376
|
attachments: ReadonlyArray<ComposerPanelAttachment>;
|
|
289
377
|
palette: ComposerPalette;
|
|
290
378
|
onRemove?: (id: string) => void;
|
|
379
|
+
onRetry?: (id: string) => void;
|
|
291
380
|
removeLabel: string;
|
|
381
|
+
retryLabel: string;
|
|
292
382
|
}
|
|
293
383
|
|
|
294
384
|
/**
|
|
@@ -296,7 +386,7 @@ interface StripProps {
|
|
|
296
386
|
* scale 0.8 → 1, fade and un-blur on arrival over 280ms `cubic-bezier(0.22, 1,
|
|
297
387
|
* 0.36, 1)`, and play it backwards on dismissal before leaving the row.
|
|
298
388
|
*/
|
|
299
|
-
export function AttachmentStrip({ attachments, palette, onRemove, removeLabel }: StripProps) {
|
|
389
|
+
export function AttachmentStrip({ attachments, palette, onRemove, onRetry, removeLabel, retryLabel }: StripProps) {
|
|
300
390
|
const [leaving, setLeaving] = useState<ReadonlyArray<ComposerPanelAttachment>>([]);
|
|
301
391
|
const previous = useRef(attachments);
|
|
302
392
|
|
|
@@ -330,7 +420,9 @@ export function AttachmentStrip({ attachments, palette, onRemove, removeLabel }:
|
|
|
330
420
|
attachment={attachment}
|
|
331
421
|
palette={palette}
|
|
332
422
|
removeLabel={removeLabel}
|
|
423
|
+
retryLabel={retryLabel}
|
|
333
424
|
onRemove={onRemove && !isLeaving ? () => onRemove(attachment.id) : undefined}
|
|
425
|
+
onRetry={onRetry && !isLeaving ? () => onRetry(attachment.id) : undefined}
|
|
334
426
|
/>
|
|
335
427
|
</PresenceTile>
|
|
336
428
|
))}
|
|
@@ -50,6 +50,7 @@ const DEFAULT_LABELS: Required<ComposerPanelLabels> = {
|
|
|
50
50
|
send: 'Send message',
|
|
51
51
|
stop: 'Stop generating',
|
|
52
52
|
remove: 'Remove',
|
|
53
|
+
retry: 'Retry',
|
|
53
54
|
};
|
|
54
55
|
|
|
55
56
|
/**
|
|
@@ -143,6 +144,7 @@ export function ComposerPanelBase({
|
|
|
143
144
|
onListeningChange,
|
|
144
145
|
attachments,
|
|
145
146
|
onRemoveAttachment,
|
|
147
|
+
onAttachmentRetry,
|
|
146
148
|
status,
|
|
147
149
|
emptyAction,
|
|
148
150
|
onKeyPress: onKeyPressProp,
|
|
@@ -235,7 +237,9 @@ export function ComposerPanelBase({
|
|
|
235
237
|
attachments={attachments ?? []}
|
|
236
238
|
palette={palette}
|
|
237
239
|
onRemove={onRemoveAttachment}
|
|
240
|
+
onRetry={onAttachmentRetry}
|
|
238
241
|
removeLabel={labels.remove}
|
|
242
|
+
retryLabel={labels.retry}
|
|
239
243
|
/>
|
|
240
244
|
</View>
|
|
241
245
|
</Collapse>
|
|
@@ -74,6 +74,9 @@ export interface ComposerPalette {
|
|
|
74
74
|
accent500: string;
|
|
75
75
|
/** `border-focus-ring`. */
|
|
76
76
|
focusRing: string;
|
|
77
|
+
/** A failed tile: the error subtle surface and its paired foreground. */
|
|
78
|
+
errorSurface: string;
|
|
79
|
+
errorText: string;
|
|
77
80
|
/** `shadow-xs`, `shadow-dropdown` and the card's raw Figma shadow. */
|
|
78
81
|
shadowXs: string;
|
|
79
82
|
shadowDropdown: string;
|
|
@@ -121,6 +124,8 @@ export function resolveComposerPalette(theme: Theme): ComposerPalette {
|
|
|
121
124
|
accent400: c.primary,
|
|
122
125
|
accent500: c.primarySubtleForeground,
|
|
123
126
|
focusRing: c.primary,
|
|
127
|
+
errorSurface: c.errorSubtle,
|
|
128
|
+
errorText: c.errorSubtleForeground,
|
|
124
129
|
shadowXs: dark ? BUTTON_SHADOW.dark : BUTTON_SHADOW.light,
|
|
125
130
|
shadowDropdown: dark ? MENU_SHADOW.dark : MENU_SHADOW.light,
|
|
126
131
|
shadowCard: '0 1px 0.5px 0 rgba(0, 0, 0, 0.02), 0 4px 2px 0 rgba(0, 0, 0, 0.02)',
|
|
@@ -80,6 +80,13 @@ export interface ComposerPanelAttachment {
|
|
|
80
80
|
* once the file has landed — that also reveals the dismiss.
|
|
81
81
|
*/
|
|
82
82
|
progress?: number;
|
|
83
|
+
/**
|
|
84
|
+
* The upload failed, and why ("Too large", "Network error"). The tile turns
|
|
85
|
+
* to the error surface and shows this in place of the name; the ring and the
|
|
86
|
+
* percentage go (any `progress` is ignored) and the dismiss comes back. With
|
|
87
|
+
* `ComposerPanel`'s `onAttachmentRetry` it also gets a retry button.
|
|
88
|
+
*/
|
|
89
|
+
error?: string;
|
|
83
90
|
}
|
|
84
91
|
|
|
85
92
|
// ---------------------------------------------------------------------------
|
|
@@ -195,6 +202,8 @@ export interface ComposerPanelLabels {
|
|
|
195
202
|
stop?: string;
|
|
196
203
|
/** Default `'Remove'` — prefixed to the file name on a tile's dismiss. */
|
|
197
204
|
remove?: string;
|
|
205
|
+
/** Default `'Retry'` — prefixed to the file name on a failed tile's retry. */
|
|
206
|
+
retry?: string;
|
|
198
207
|
}
|
|
199
208
|
|
|
200
209
|
export interface ComposerPanelProps {
|
|
@@ -256,6 +265,8 @@ export interface ComposerPanelProps {
|
|
|
256
265
|
/** Tiles above the prompt. Each one's `progress` drives its upload ring. */
|
|
257
266
|
attachments?: ReadonlyArray<ComposerPanelAttachment>;
|
|
258
267
|
onRemoveAttachment?: (id: string) => void;
|
|
268
|
+
/** Draws a retry button on every tile with an `error`; called with its id. */
|
|
269
|
+
onAttachmentRetry?: (id: string) => void;
|
|
259
270
|
|
|
260
271
|
/** The tab on the card's top edge — typically `<ComposerPanelStatusTab />`. */
|
|
261
272
|
status?: ReactNode;
|
|
@@ -4,8 +4,11 @@ import type { ComposerAttachmentsProps, ComposerPanelAttachment } from './types'
|
|
|
4
4
|
|
|
5
5
|
const TICK_MS = 50;
|
|
6
6
|
|
|
7
|
-
/**
|
|
8
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Anything carrying a progress value — the closed ring at 100 included — is
|
|
9
|
+
* still the queue's, unless it failed: a failed file is done with.
|
|
10
|
+
*/
|
|
11
|
+
const inFlight = (attachment: ComposerPanelAttachment) => attachment.progress !== undefined && !attachment.error;
|
|
9
12
|
|
|
10
13
|
/** Keep the parent's order; keep our progress for files we already know. */
|
|
11
14
|
function reconcile(
|
|
@@ -291,7 +291,13 @@ export function InputOtp({
|
|
|
291
291
|
autoFocus={autoFocus && index === 0}
|
|
292
292
|
// Long enough to accept a full autofilled code in one box.
|
|
293
293
|
maxLength={length}
|
|
294
|
-
|
|
294
|
+
// Typing into a filled box must REPLACE its digit, so the digit is
|
|
295
|
+
// selected on focus. Web's `selectTextOnFocus` does that. Android's
|
|
296
|
+
// does not: it selects on the input's next layout, and focusing a box
|
|
297
|
+
// lays nothing out, so the keystroke landed beside the old digit and
|
|
298
|
+
// `writeFrom` spread both across two boxes. Native selects in
|
|
299
|
+
// `onFocus` instead (see Search, OxyHQ/Mention#1126).
|
|
300
|
+
selectTextOnFocus={IS_WEB}
|
|
295
301
|
caretHidden={false}
|
|
296
302
|
editable={!disabled}
|
|
297
303
|
accessibilityLabel={`Digit ${index + 1} of ${length}`}
|
|
@@ -300,7 +306,10 @@ export function InputOtp({
|
|
|
300
306
|
value={digit === ' ' ? '' : digit}
|
|
301
307
|
onChangeText={(text) => writeFrom(index, text)}
|
|
302
308
|
onKeyPress={(event) => onKeyPress(event, index)}
|
|
303
|
-
onFocus={() =>
|
|
309
|
+
onFocus={() => {
|
|
310
|
+
setFocusedIndex(index);
|
|
311
|
+
if (!IS_WEB && digit !== '' && digit !== ' ') inputsRef.current[index]?.setSelection(0, 1);
|
|
312
|
+
}}
|
|
304
313
|
onBlur={() => setFocusedIndex((current) => (current === index ? null : current))}
|
|
305
314
|
keyboardAppearance={theme.isDark ? 'dark' : 'light'}
|
|
306
315
|
style={[
|