fix: require content to create workspace — no more empty submissions
Replace allowEmptySubmit/emptySubmitLabel with hasExternalContent prop that treats GitHub items the same as text and images in submit gating.
This commit is contained in:
@@ -3,7 +3,7 @@ export type AgentInputSubmitResult = "noop" | "queued" | "submitted" | "failed";
|
|||||||
export interface AgentInputSubmitActionInput<TImage> {
|
export interface AgentInputSubmitActionInput<TImage> {
|
||||||
message: string;
|
message: string;
|
||||||
imageAttachments?: TImage[];
|
imageAttachments?: TImage[];
|
||||||
allowEmptySubmit?: boolean;
|
hasExternalContent?: boolean;
|
||||||
forceSend?: boolean;
|
forceSend?: boolean;
|
||||||
isAgentRunning: boolean;
|
isAgentRunning: boolean;
|
||||||
canSubmit: boolean;
|
canSubmit: boolean;
|
||||||
@@ -23,7 +23,7 @@ export async function submitAgentInput<TImage>(
|
|||||||
const trimmedMessage = input.message.trim();
|
const trimmedMessage = input.message.trim();
|
||||||
const imageAttachments = input.imageAttachments;
|
const imageAttachments = input.imageAttachments;
|
||||||
|
|
||||||
if (!trimmedMessage && !imageAttachments?.length && !input.allowEmptySubmit) {
|
if (!trimmedMessage && !imageAttachments?.length && !input.hasExternalContent) {
|
||||||
return "noop";
|
return "noop";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -65,9 +65,8 @@ interface ComposerProps {
|
|||||||
serverId: string;
|
serverId: string;
|
||||||
isInputActive: boolean;
|
isInputActive: boolean;
|
||||||
onSubmitMessage?: (payload: MessagePayload) => Promise<void>;
|
onSubmitMessage?: (payload: MessagePayload) => Promise<void>;
|
||||||
allowEmptySubmit?: boolean;
|
/** When true, the submit button is enabled even without text or images (e.g. external attachment selected). */
|
||||||
/** Label to show on the submit button when the input is empty and allowEmptySubmit is true. */
|
hasExternalContent?: boolean;
|
||||||
emptySubmitLabel?: string;
|
|
||||||
/** Externally controlled loading state. When true, disables the submit button. */
|
/** Externally controlled loading state. When true, disables the submit button. */
|
||||||
isSubmitLoading?: boolean;
|
isSubmitLoading?: boolean;
|
||||||
/** When true, blurs the input immediately when submitting. */
|
/** When true, blurs the input immediately when submitting. */
|
||||||
@@ -105,8 +104,7 @@ export function Composer({
|
|||||||
serverId,
|
serverId,
|
||||||
isInputActive,
|
isInputActive,
|
||||||
onSubmitMessage,
|
onSubmitMessage,
|
||||||
allowEmptySubmit = false,
|
hasExternalContent = false,
|
||||||
emptySubmitLabel,
|
|
||||||
isSubmitLoading = false,
|
isSubmitLoading = false,
|
||||||
blurOnSubmit = false,
|
blurOnSubmit = false,
|
||||||
value,
|
value,
|
||||||
@@ -354,7 +352,7 @@ export function Composer({
|
|||||||
await submitAgentInput({
|
await submitAgentInput({
|
||||||
message,
|
message,
|
||||||
imageAttachments,
|
imageAttachments,
|
||||||
allowEmptySubmit,
|
hasExternalContent,
|
||||||
forceSend,
|
forceSend,
|
||||||
isAgentRunning: agentState.status === "running",
|
isAgentRunning: agentState.status === "running",
|
||||||
// Parent-managed submits are still valid submit paths even when the
|
// Parent-managed submits are still valid submit paths even when the
|
||||||
@@ -746,8 +744,7 @@ export function Composer({
|
|||||||
value={userInput}
|
value={userInput}
|
||||||
onChangeText={setUserInput}
|
onChangeText={setUserInput}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
allowEmptySubmit={allowEmptySubmit}
|
hasExternalContent={hasExternalContent}
|
||||||
emptySubmitLabel={emptySubmitLabel}
|
|
||||||
isSubmitDisabled={isProcessing || isSubmitLoading}
|
isSubmitDisabled={isProcessing || isSubmitLoading}
|
||||||
isSubmitLoading={isProcessing || isSubmitLoading}
|
isSubmitLoading={isProcessing || isSubmitLoading}
|
||||||
images={selectedImages}
|
images={selectedImages}
|
||||||
|
|||||||
@@ -62,9 +62,8 @@ export interface MessageInputProps {
|
|||||||
value: string;
|
value: string;
|
||||||
onChangeText: (text: string) => void;
|
onChangeText: (text: string) => void;
|
||||||
onSubmit: (payload: MessagePayload) => void;
|
onSubmit: (payload: MessagePayload) => void;
|
||||||
allowEmptySubmit?: boolean;
|
/** When true, the submit button is enabled even without text or images (e.g. external attachment selected). */
|
||||||
/** Label to show on the submit button when the input is empty and allowEmptySubmit is true. */
|
hasExternalContent?: boolean;
|
||||||
emptySubmitLabel?: string;
|
|
||||||
isSubmitDisabled?: boolean;
|
isSubmitDisabled?: boolean;
|
||||||
isSubmitLoading?: boolean;
|
isSubmitLoading?: boolean;
|
||||||
images?: ImageAttachment[];
|
images?: ImageAttachment[];
|
||||||
@@ -201,8 +200,7 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(funct
|
|||||||
value,
|
value,
|
||||||
onChangeText,
|
onChangeText,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
allowEmptySubmit = false,
|
hasExternalContent = false,
|
||||||
emptySubmitLabel,
|
|
||||||
isSubmitDisabled = false,
|
isSubmitDisabled = false,
|
||||||
isSubmitLoading = false,
|
isSubmitLoading = false,
|
||||||
images = [],
|
images = [],
|
||||||
@@ -573,7 +571,7 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(funct
|
|||||||
|
|
||||||
const handleSendMessage = useCallback(() => {
|
const handleSendMessage = useCallback(() => {
|
||||||
const trimmed = value.trim();
|
const trimmed = value.trim();
|
||||||
if (!trimmed && images.length === 0 && !allowEmptySubmit) return;
|
if (!trimmed && images.length === 0 && !hasExternalContent) return;
|
||||||
const payload = {
|
const payload = {
|
||||||
text: trimmed,
|
text: trimmed,
|
||||||
images: images.length > 0 ? images : undefined,
|
images: images.length > 0 ? images : undefined,
|
||||||
@@ -583,7 +581,7 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(funct
|
|||||||
inputHeightRef.current = MIN_INPUT_HEIGHT;
|
inputHeightRef.current = MIN_INPUT_HEIGHT;
|
||||||
setInputHeight(MIN_INPUT_HEIGHT);
|
setInputHeight(MIN_INPUT_HEIGHT);
|
||||||
onHeightChange?.(MIN_INPUT_HEIGHT);
|
onHeightChange?.(MIN_INPUT_HEIGHT);
|
||||||
}, [value, images, onSubmit, isAgentRunning, onHeightChange, allowEmptySubmit]);
|
}, [value, images, onSubmit, isAgentRunning, onHeightChange, hasExternalContent]);
|
||||||
|
|
||||||
const handleQueueMessage = useCallback(() => {
|
const handleQueueMessage = useCallback(() => {
|
||||||
if (!onQueue) return;
|
if (!onQueue) return;
|
||||||
@@ -935,10 +933,8 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(funct
|
|||||||
|
|
||||||
const hasImages = images.length > 0;
|
const hasImages = images.length > 0;
|
||||||
const hasRealContent = value.trim().length > 0 || hasImages;
|
const hasRealContent = value.trim().length > 0 || hasImages;
|
||||||
const hasSendableContent = hasRealContent || allowEmptySubmit;
|
const hasSendableContent = hasRealContent || hasExternalContent;
|
||||||
const shouldShowSendButton = hasSendableContent || isSubmitLoading;
|
const shouldShowSendButton = hasSendableContent || isSubmitLoading;
|
||||||
const showEmptySubmitLabel =
|
|
||||||
allowEmptySubmit && emptySubmitLabel && !hasRealContent && !isSubmitLoading;
|
|
||||||
const canPressLoadingButton = isSubmitLoading && typeof onSubmitLoadingPress === "function";
|
const canPressLoadingButton = isSubmitLoading && typeof onSubmitLoadingPress === "function";
|
||||||
const isSendButtonDisabled =
|
const isSendButtonDisabled =
|
||||||
disabled || (!canPressLoadingButton && (isSubmitDisabled || isSubmitLoading));
|
disabled || (!canPressLoadingButton && (isSubmitDisabled || isSubmitLoading));
|
||||||
@@ -1170,19 +1166,15 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(funct
|
|||||||
<TooltipTrigger
|
<TooltipTrigger
|
||||||
onPress={canPressLoadingButton ? onSubmitLoadingPress : handleDefaultSendAction}
|
onPress={canPressLoadingButton ? onSubmitLoadingPress : handleDefaultSendAction}
|
||||||
disabled={isSendButtonDisabled}
|
disabled={isSendButtonDisabled}
|
||||||
accessibilityLabel={
|
accessibilityLabel={submitAccessibilityLabel}
|
||||||
showEmptySubmitLabel ? emptySubmitLabel : submitAccessibilityLabel
|
|
||||||
}
|
|
||||||
accessibilityRole="button"
|
accessibilityRole="button"
|
||||||
style={[
|
style={[
|
||||||
showEmptySubmitLabel ? styles.emptySubmitButton : styles.sendButton,
|
styles.sendButton,
|
||||||
isSendButtonDisabled && styles.buttonDisabled,
|
isSendButtonDisabled && styles.buttonDisabled,
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{isSubmitLoading ? (
|
{isSubmitLoading ? (
|
||||||
<ActivityIndicator size="small" color="white" />
|
<ActivityIndicator size="small" color="white" />
|
||||||
) : showEmptySubmitLabel ? (
|
|
||||||
<Text style={styles.emptySubmitLabel}>{emptySubmitLabel}</Text>
|
|
||||||
) : (
|
) : (
|
||||||
<ArrowUp size={buttonIconSize} color="white" />
|
<ArrowUp size={buttonIconSize} color="white" />
|
||||||
)}
|
)}
|
||||||
@@ -1190,11 +1182,7 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(funct
|
|||||||
<TooltipContent side="top" align="center" offset={8}>
|
<TooltipContent side="top" align="center" offset={8}>
|
||||||
<View style={styles.tooltipRow}>
|
<View style={styles.tooltipRow}>
|
||||||
<Text style={styles.tooltipText}>
|
<Text style={styles.tooltipText}>
|
||||||
{showEmptySubmitLabel
|
{defaultActionQueues ? "Queue" : "Send"}
|
||||||
? emptySubmitLabel
|
|
||||||
: defaultActionQueues
|
|
||||||
? "Queue"
|
|
||||||
: "Send"}
|
|
||||||
</Text>
|
</Text>
|
||||||
{sendKeys ? <Shortcut chord={sendKeys} style={styles.tooltipShortcut} /> : null}
|
{sendKeys ? <Shortcut chord={sendKeys} style={styles.tooltipShortcut} /> : null}
|
||||||
</View>
|
</View>
|
||||||
@@ -1379,18 +1367,6 @@ const styles = StyleSheet.create(((theme: any) => ({
|
|||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
},
|
},
|
||||||
emptySubmitButton: {
|
|
||||||
height: 28,
|
|
||||||
borderRadius: theme.borderRadius.full,
|
|
||||||
backgroundColor: theme.colors.accent,
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
paddingHorizontal: theme.spacing[3],
|
|
||||||
},
|
|
||||||
emptySubmitLabel: {
|
|
||||||
color: "white",
|
|
||||||
fontSize: theme.fontSize.sm,
|
|
||||||
},
|
|
||||||
iconButtonHovered: {
|
iconButtonHovered: {
|
||||||
backgroundColor: theme.colors.surface2,
|
backgroundColor: theme.colors.surface2,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -289,8 +289,7 @@ export function NewWorkspaceScreen({
|
|||||||
serverId={serverId}
|
serverId={serverId}
|
||||||
isInputActive={true}
|
isInputActive={true}
|
||||||
onSubmitMessage={handleCreateChatAgent}
|
onSubmitMessage={handleCreateChatAgent}
|
||||||
allowEmptySubmit
|
hasExternalContent={selectedGithubItem !== null}
|
||||||
emptySubmitLabel="Create"
|
|
||||||
isSubmitLoading={pendingAction === "chat"}
|
isSubmitLoading={pendingAction === "chat"}
|
||||||
blurOnSubmit={true}
|
blurOnSubmit={true}
|
||||||
value={chatDraft.text}
|
value={chatDraft.text}
|
||||||
|
|||||||
@@ -287,7 +287,6 @@ export function WorkspaceDraftAgentTab({
|
|||||||
serverId={serverId}
|
serverId={serverId}
|
||||||
isInputActive={isPaneFocused}
|
isInputActive={isPaneFocused}
|
||||||
onSubmitMessage={handleCreateFromInput}
|
onSubmitMessage={handleCreateFromInput}
|
||||||
allowEmptySubmit={false}
|
|
||||||
isSubmitLoading={isSubmitting}
|
isSubmitLoading={isSubmitting}
|
||||||
blurOnSubmit={true}
|
blurOnSubmit={true}
|
||||||
value={draftInput.text}
|
value={draftInput.text}
|
||||||
|
|||||||
Reference in New Issue
Block a user