feat(rain): add date preset picker and reduce time clarification prompts.
Memind CI / Test, build, and release guards (push) Successful in 5m5s

Default to last 3 days in UI and backend, only ask when time is truly ambiguous, and treat「做成页面」as HTML report pages without follow-up questions.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-09-07 16:54:11 +08:00
parent 7cc3fb7f8a
commit d2d463b1db
11 changed files with 230 additions and 24 deletions
+27
View File
@@ -33,6 +33,7 @@ import {
import { AvatarPicker } from './AvatarPicker';
import { ChatSkillPicker } from './ChatSkillPicker';
import { ChatSkillIcon } from './ChatSkillIcons';
import { RainTimeRangeBar } from './RainTimeRangeBar';
import { PageTemplateShopModal } from './PageTemplateShopModal';
import { ChatLoadingSpinner } from './ChatLoadingSpinner';
import { ChatSharePreviewModal } from './ChatSharePreviewModal';
@@ -42,6 +43,11 @@ import { PageSaveDialog } from './PageSaveDialog';
import { VoiceInputButton } from './VoiceInputButton';
import type { CapabilityMap, ChatFileAttachment, ChatState, Message, PortalUser, Session, ToolConfirmation } from '../types';
import type { MindSpaceSaveCategory } from '../types';
import {
DEFAULT_RAIN_TIME_PRESET,
RAIN_SKILL_ID,
type RainTimePresetId,
} from '../utils/rainTimeRange';
const CHAT_PLACEHOLDER_PROMPTS = [
'帮我写一篇温柔一点的小短文',
@@ -202,6 +208,7 @@ export function ChatPanel({
pgRequired?: boolean;
imageGenerationMode?: ImageGenerationMode;
selectedChatSkill?: string;
rainTimePreset?: RainTimePresetId;
fileAttachments?: ChatFileAttachment[];
},
) => void | Promise<void>;
@@ -241,6 +248,8 @@ export function ChatPanel({
const [deepReasoningTipVisible, setDeepReasoningTipVisible] = useState(false);
const [chatControlOnboardingStep, setChatControlOnboardingStep] = useState<0 | 1 | null>(null);
const [templateShopOpen, setTemplateShopOpen] = useState(false);
const [activeRainSkill, setActiveRainSkill] = useState(false);
const [rainTimePreset, setRainTimePreset] = useState<RainTimePresetId>(DEFAULT_RAIN_TIME_PRESET);
const [activeTemplatePrefill, setActiveTemplatePrefill] = useState<{
skillId: string;
label: string;
@@ -840,8 +849,12 @@ export function ChatPanel({
pgRequired: false,
imageGenerationMode: 'auto',
selectedChatSkill,
...(selectedChatSkill === RAIN_SKILL_ID ? { rainTimePreset } : {}),
fileAttachments: fileAttachmentsToSend,
});
if (selectedChatSkill === RAIN_SKILL_ID) {
setActiveRainSkill(false);
}
uploadedImages.forEach(revokePendingImage);
setPendingImages([]);
setPendingFiles([]);
@@ -868,6 +881,7 @@ export function ChatPanel({
}, [
activeTemplatePrefill,
forceDeepReasoning,
rainTimePreset,
input,
onSubmit,
onUploadFile,
@@ -1255,6 +1269,13 @@ export function ChatPanel({
</button>
</div>
)}
{activeRainSkill && !taskInputStatusText && (
<RainTimeRangeBar
value={rainTimePreset}
disabled={voiceDisabled}
onChange={setRainTimePreset}
/>
)}
<div className={`chat-input-row${showHomeWelcome ? ' chat-input-row-home' : ''}`}>
<div className={`chat-input-shell${showHomeWelcome ? ' chat-input-shell-home' : ''}`}>
{canUpload && (
@@ -1343,6 +1364,12 @@ export function ChatPanel({
}
pendingSkillRef.current = skillId ?? null;
setActiveTemplatePrefill(null);
if (skillId === RAIN_SKILL_ID) {
setActiveRainSkill(true);
setRainTimePreset(DEFAULT_RAIN_TIME_PRESET);
} else {
setActiveRainSkill(false);
}
setInput((current) => mergeChatSkillPromptWithInput(prompt, current));
}}
/>
+1
View File
@@ -588,6 +588,7 @@ export function ChatView({
pgRequired: options?.pgRequired,
imageGenerationMode: options?.imageGenerationMode,
selectedChatSkill: options?.selectedChatSkill,
rainTimePreset: options?.rainTimePreset,
fileAttachments: options?.fileAttachments,
},
imageUrls,
+37
View File
@@ -0,0 +1,37 @@
import {
DEFAULT_RAIN_TIME_PRESET,
RAIN_TIME_PRESET_OPTIONS,
type RainTimePresetId,
} from '../utils/rainTimeRange';
export function RainTimeRangeBar({
value,
disabled,
onChange,
}: {
value?: RainTimePresetId;
disabled?: boolean;
onChange: (preset: RainTimePresetId) => void;
}) {
const active = value ?? DEFAULT_RAIN_TIME_PRESET;
return (
<div className="rain-time-range-bar" role="group" aria-label="Rain 分析时间范围">
<span className="rain-time-range-bar-label"></span>
<div className="rain-time-range-bar-options">
{RAIN_TIME_PRESET_OPTIONS.map((option) => (
<button
key={option.id}
type="button"
className={`rain-time-range-option${active === option.id ? ' is-active' : ''}`}
disabled={disabled}
aria-pressed={active === option.id}
onClick={() => onChange(option.id)}
>
{option.label}
</button>
))}
</div>
</div>
);
}
+2
View File
@@ -100,6 +100,7 @@ type ChatSubmitOptions = {
pgRequired?: boolean;
imageGenerationMode?: ImageGenerationMode;
selectedChatSkill?: string;
rainTimePreset?: import('../utils/rainTimeRange').RainTimePresetId;
fileAttachments?: ChatFileAttachment[];
goalRunId?: string;
};
@@ -1874,6 +1875,7 @@ export function useTKMindChat(
...(options?.pgRequired ? { pgRequired: true } : {}),
imageGenerationMode: options?.imageGenerationMode ?? 'auto',
...(options?.selectedChatSkill ? { selectedChatSkill: options.selectedChatSkill } : {}),
...(options?.rainTimePreset ? { rainTimePreset: options.rainTimePreset } : {}),
},
};
+53
View File
@@ -3162,6 +3162,59 @@ body,
flex: 0 0 auto;
}
.rain-time-range-bar {
display: flex;
align-items: center;
gap: 10px;
margin: 0 0 8px;
padding: 8px 10px;
border: 1px solid var(--color-border-input);
border-radius: var(--radius-lg);
background: var(--color-bg-elevated);
}
.rain-time-range-bar-label {
flex: 0 0 auto;
font-size: 12px;
font-weight: 600;
color: var(--color-text-secondary);
white-space: nowrap;
}
.rain-time-range-bar-options {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.rain-time-range-option {
min-height: 28px;
padding: 4px 10px;
border: 1px solid var(--color-border-input);
border-radius: 999px;
background: transparent;
color: var(--color-text-secondary);
font: inherit;
font-size: 12px;
cursor: pointer;
}
.rain-time-range-option:hover:not(:disabled) {
color: var(--color-text-primary);
border-color: var(--color-border-strong, var(--color-border-input));
}
.rain-time-range-option.is-active {
color: var(--color-text-primary);
border-color: rgba(121, 146, 255, 0.55);
background: rgba(121, 146, 255, 0.12);
}
.rain-time-range-option:disabled {
opacity: 0.55;
cursor: not-allowed;
}
.chat-deep-reasoning-toggle {
position: relative;
display: inline-flex;
+12
View File
@@ -0,0 +1,12 @@
export type RainTimePresetId = 'today' | 'yesterday' | '3d' | '7d';
export const RAIN_SKILL_ID = 'rain';
export const RAIN_TIME_PRESET_OPTIONS: ReadonlyArray<{ id: RainTimePresetId; label: string }> = [
{ id: 'today', label: '今天' },
{ id: 'yesterday', label: '昨天' },
{ id: '3d', label: '近3天' },
{ id: '7d', label: '近7天' },
];
export const DEFAULT_RAIN_TIME_PRESET: RainTimePresetId = '3d';