feat: integrate action mode into messaging components and enhance team provisioning banner
- Updated TeamDetailView to include action mode in message sending functionality. - Enhanced TeamProvisioningBanner to display a message based on the online status of team members. - Added ActionModeSelector to SendMessageDialog for selecting action modes during message composition. - Updated tests to validate action mode handling and ensure proper messaging behavior.
This commit is contained in:
parent
2d0d390442
commit
5e4d10b950
4 changed files with 50 additions and 8 deletions
|
|
@ -1877,12 +1877,18 @@ export const TeamDetailView = ({ teamName }: TeamDetailViewProps): React.JSX.Ele
|
|||
sending={sendingMessage}
|
||||
sendError={sendMessageError}
|
||||
lastResult={lastSendMessageResult}
|
||||
onSend={(member, text, summary, attachments) => {
|
||||
onSend={(member, text, summary, attachments, actionMode) => {
|
||||
void (async () => {
|
||||
const sentAtMs = Date.now();
|
||||
setPendingRepliesByMember((prev) => ({ ...prev, [member]: sentAtMs }));
|
||||
try {
|
||||
await sendTeamMessage(teamName, { member, text, summary, attachments });
|
||||
await sendTeamMessage(teamName, {
|
||||
member,
|
||||
text,
|
||||
summary,
|
||||
attachments,
|
||||
actionMode,
|
||||
});
|
||||
} catch {
|
||||
setPendingRepliesByMember((prev) => {
|
||||
if (prev[member] !== sentAtMs) return prev;
|
||||
|
|
|
|||
|
|
@ -29,10 +29,11 @@ function findProgressForTeam(
|
|||
export const TeamProvisioningBanner = ({
|
||||
teamName,
|
||||
}: TeamProvisioningBannerProps): React.JSX.Element | null => {
|
||||
const { provisioningRuns, cancelProvisioning } = useStore(
|
||||
const { provisioningRuns, cancelProvisioning, teamMembers } = useStore(
|
||||
useShallow((s) => ({
|
||||
provisioningRuns: s.provisioningRuns,
|
||||
cancelProvisioning: s.cancelProvisioning,
|
||||
teamMembers: s.selectedTeamData?.members,
|
||||
}))
|
||||
);
|
||||
|
||||
|
|
@ -105,14 +106,21 @@ export const TeamProvisioningBanner = ({
|
|||
);
|
||||
}
|
||||
|
||||
const allTeammatesOnline =
|
||||
teamMembers != null &&
|
||||
teamMembers.length > 0 &&
|
||||
teamMembers.every((m) => m.status === 'active' || m.status === 'idle');
|
||||
|
||||
if (isReady) {
|
||||
const readyMessage = allTeammatesOnline
|
||||
? `Team launched — all ${teamMembers!.length} teammates online`
|
||||
: 'Team launched — teammates may still be starting';
|
||||
|
||||
return (
|
||||
<div className="mb-3">
|
||||
<div className="flex items-center gap-2 rounded-md border border-[var(--step-done-border)] bg-[var(--step-done-bg)] px-3 py-2">
|
||||
<CheckCircle2 size={14} className="shrink-0 text-[var(--step-done-text)]" />
|
||||
<p className="flex-1 text-xs text-[var(--step-success-text)]">
|
||||
Team launched — teammates may still be starting
|
||||
</p>
|
||||
<p className="flex-1 text-xs text-[var(--step-success-text)]">{readyMessage}</p>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|||
import { MarkdownViewer } from '@renderer/components/chat/viewers/MarkdownViewer';
|
||||
import { AttachmentPreviewList } from '@renderer/components/team/attachments/AttachmentPreviewList';
|
||||
import { DropZoneOverlay } from '@renderer/components/team/attachments/DropZoneOverlay';
|
||||
import { ActionModeSelector } from '@renderer/components/team/messages/ActionModeSelector';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
|
|
@ -28,6 +29,7 @@ import { AlertCircle, ImagePlus, Send, X } from 'lucide-react';
|
|||
|
||||
import { MemberBadge } from '../MemberBadge';
|
||||
|
||||
import type { ActionMode } from '@renderer/components/team/messages/ActionModeSelector';
|
||||
import type { InlineChip } from '@renderer/types/inlineChip';
|
||||
import type { MentionSuggestion } from '@renderer/types/mention';
|
||||
import type { AttachmentPayload, ResolvedTeamMember, SendMessageResult } from '@shared/types';
|
||||
|
|
@ -55,7 +57,8 @@ interface SendMessageDialogProps {
|
|||
member: string,
|
||||
text: string,
|
||||
summary?: string,
|
||||
attachments?: AttachmentPayload[]
|
||||
attachments?: AttachmentPayload[],
|
||||
actionMode?: ActionMode
|
||||
) => void;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
|
@ -90,6 +93,7 @@ export const SendMessageDialog = ({
|
|||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const [imageRestrictionError, setImageRestrictionError] = useState<string | null>(null);
|
||||
const imageRestrictionTimerRef = useRef(0);
|
||||
const [actionMode, setActionMode] = useState<ActionMode>('do');
|
||||
|
||||
const {
|
||||
attachments,
|
||||
|
|
@ -107,6 +111,14 @@ export const SendMessageDialog = ({
|
|||
const supportsAttachments = isLeadRecipient && !!isTeamAlive;
|
||||
const canAttach = supportsAttachments && canAddMore;
|
||||
|
||||
useEffect(() => {
|
||||
if (isLeadRecipient) {
|
||||
setActionMode('delegate');
|
||||
} else {
|
||||
setActionMode((prev) => (prev === 'delegate' ? 'do' : prev));
|
||||
}
|
||||
}, [isLeadRecipient]);
|
||||
|
||||
const [pendingAutoClose, setPendingAutoClose] = useState(false);
|
||||
// Reset form on open transition (avoid setState in render)
|
||||
useEffect(() => {
|
||||
|
|
@ -195,7 +207,13 @@ export const SendMessageDialog = ({
|
|||
|
||||
const handleSubmit = (): void => {
|
||||
if (!canSend) return;
|
||||
onSend(member.trim(), finalText, trimmedText, attachments.length > 0 ? attachments : undefined);
|
||||
onSend(
|
||||
member.trim(),
|
||||
finalText,
|
||||
trimmedText,
|
||||
attachments.length > 0 ? attachments : undefined,
|
||||
actionMode
|
||||
);
|
||||
textDraft.clearDraft();
|
||||
chipDraft.clearChipDraft();
|
||||
clearAttachments();
|
||||
|
|
@ -428,6 +446,13 @@ export const SendMessageDialog = ({
|
|||
maxRows={12}
|
||||
maxLength={MAX_TEXT_LENGTH}
|
||||
disabled={sending}
|
||||
cornerActionLeft={
|
||||
<ActionModeSelector
|
||||
value={actionMode}
|
||||
onChange={setActionMode}
|
||||
showDelegate={isLeadRecipient}
|
||||
/>
|
||||
}
|
||||
cornerAction={
|
||||
<button
|
||||
type="button"
|
||||
|
|
|
|||
|
|
@ -276,6 +276,9 @@ describe('TeamProvisioningService post-compact lifecycle', () => {
|
|||
// Should contain persistent context
|
||||
expect(text).toContain('Constraints:');
|
||||
expect(text).toContain('Do NOT call TeamDelete');
|
||||
expect(text).toContain('TURN ACTION MODE PROTOCOL (HIGHEST PRIORITY FOR EACH USER TURN):');
|
||||
expect(text).toContain('ASK: Strict read-only conversation mode.');
|
||||
expect(text).toContain('DELEGATE: Strict orchestration mode for leads.');
|
||||
expect(text).toContain('cross_team_send');
|
||||
expect(text).toContain('cross_team_list_targets');
|
||||
expect(text).toContain('cross_team_get_outbox');
|
||||
|
|
|
|||
Loading…
Reference in a new issue