mirror of
https://github.com/jjbrunton/Sauci.git
synced 2026-11-02 10:19:43 +00:00
Add question types, nudge feature, and share modal
- Add new question card components (photo, audio, text, who-likely) - Add share preview modal for matches - Add nudge notification system with rate limiting - Add session-based notification improvements - Add match confetti celebration animation - Update onboarding flow with paywall tracking - Remove deprecated swipe card and tutorial components - Update GlassCard/GlassInput to use solid backgrounds - Fix routing issues after packs screen removal
This commit is contained in:
1 parent
8612b16b93
commit
1daf386765
114 files changed
+14877
-7911
No files matched your search
@@ -114,7 +114,7 @@ export function AiGeneratorDialog({
|
||||
context={context}
|
||||
config={generation.config}
|
||||
onCountChange={generation.setCount}
|
||||
onToneChange={generation.setTone}
|
||||
onExplicitChange={generation.setExplicit}
|
||||
onCrudeLangChange={generation.setCrudeLang}
|
||||
onInspirationChange={generation.setInspiration}
|
||||
/>
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { TONE_LEVELS, type ToneLevel } from '@/lib/openai';
|
||||
import type { GeneratorType, GeneratorContext, GenerationConfig } from './hooks/useAiGeneration';
|
||||
|
||||
// =============================================================================
|
||||
@@ -15,7 +13,7 @@ interface GenerationFormProps {
|
||||
context?: GeneratorContext;
|
||||
config: GenerationConfig;
|
||||
onCountChange: (count: number) => void;
|
||||
onToneChange: (tone: ToneLevel) => void;
|
||||
onExplicitChange: (isExplicit: boolean) => void;
|
||||
onCrudeLangChange: (crudeLang: boolean) => void;
|
||||
onInspirationChange: (inspiration: string) => void;
|
||||
}
|
||||
@@ -29,12 +27,10 @@ export function GenerationForm({
|
||||
context,
|
||||
config,
|
||||
onCountChange,
|
||||
onToneChange,
|
||||
onExplicitChange,
|
||||
onCrudeLangChange,
|
||||
onInspirationChange,
|
||||
}: GenerationFormProps) {
|
||||
const isExplicit = config.tone >= 4;
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{/* Question Count (questions only) */}
|
||||
@@ -53,42 +49,15 @@ export function GenerationForm({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Intimacy Level Selector (questions only) */}
|
||||
{type === 'questions' && (
|
||||
<div className="space-y-2">
|
||||
<Label>Intimacy Level</Label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{TONE_LEVELS.map((t) => (
|
||||
<Button
|
||||
key={t.level}
|
||||
type="button"
|
||||
variant={config.tone === t.level ? 'default' : 'outline'}
|
||||
size="sm"
|
||||
onClick={() => onToneChange(t.level as ToneLevel)}
|
||||
className={config.tone === t.level ? 'ring-2 ring-offset-1' : ''}
|
||||
title={t.description}
|
||||
>
|
||||
{t.level}. {t.label}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{TONE_LEVELS.find(t => t.level === config.tone)?.description}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Wild/Explicit Toggle (for other generators) */}
|
||||
{(type === 'category-ideas' || type === 'category-pack-ideas' || type === 'pack') && (
|
||||
<div className="flex items-center space-x-2">
|
||||
<Switch
|
||||
id="explicit-mode"
|
||||
checked={isExplicit}
|
||||
onCheckedChange={(checked: boolean) => onToneChange(checked ? 5 : 3)}
|
||||
/>
|
||||
<Label htmlFor="explicit-mode">Include Wild/Adult Ideas</Label>
|
||||
</div>
|
||||
)}
|
||||
{/* Explicit Toggle */}
|
||||
<div className="flex items-center space-x-2">
|
||||
<Switch
|
||||
id="explicit-mode"
|
||||
checked={config.isExplicit}
|
||||
onCheckedChange={onExplicitChange}
|
||||
/>
|
||||
<Label htmlFor="explicit-mode">Explicit Content</Label>
|
||||
</div>
|
||||
|
||||
{/* Crude Language Override Toggle */}
|
||||
<div className="flex items-center space-x-2">
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
Bot,
|
||||
} from 'lucide-react';
|
||||
import {
|
||||
INTENSITY_LEVELS,
|
||||
type GeneratedQuestion,
|
||||
type QuestionReview,
|
||||
type CouncilGenerationResult,
|
||||
@@ -199,16 +198,6 @@ function QuestionItem({
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
{/* Intensity Badge */}
|
||||
<span
|
||||
className={`inline-block text-xs px-2 py-0.5 rounded text-white ${
|
||||
INTENSITY_LEVELS[question.intensity - 1]?.color || 'bg-muted'
|
||||
}`}
|
||||
title={INTENSITY_LEVELS[question.intensity - 1]?.description}
|
||||
>
|
||||
{question.intensity} - {INTENSITY_LEVELS[question.intensity - 1]?.label || 'Unknown'}
|
||||
</span>
|
||||
|
||||
{/* Source Generator Badge (cherry-pick mode) */}
|
||||
{selectionMode === 'cherry_pick' && question.sourceGeneratorModel && (
|
||||
<span className="inline-flex items-center gap-1 text-xs px-2 py-0.5 rounded bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400">
|
||||
@@ -228,7 +217,7 @@ function QuestionItem({
|
||||
: 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400'
|
||||
}`}
|
||||
title={review.scores
|
||||
? `Scores: ${review.scores.guidelineCompliance}/${review.scores.creativity}/${review.scores.clarity}/${review.scores.intensityAccuracy}`
|
||||
? `Scores: ${review.scores.guidelineCompliance}/${review.scores.creativity}/${review.scores.clarity}`
|
||||
: ''
|
||||
}
|
||||
>
|
||||
|
||||
@@ -91,15 +91,6 @@ export function ReviewSummary({
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Intensity distribution for cherry-pick */}
|
||||
{selectionMode === 'cherry_pick' && cherryPickResult && Object.keys(cherryPickResult.intensityDistribution).length > 0 && (
|
||||
<p>
|
||||
Intensity distribution: {Object.entries(cherryPickResult.intensityDistribution)
|
||||
.sort(([a], [b]) => Number(a) - Number(b))
|
||||
.map(([level, count]) => `L${level}:${count}`)
|
||||
.join(', ')}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
type GeneratedQuestion,
|
||||
type GeneratedCategoryIdea,
|
||||
type GeneratedPackIdea,
|
||||
type ToneLevel,
|
||||
type QuestionReview,
|
||||
type CouncilGenerationResult,
|
||||
type GenerationCandidate,
|
||||
@@ -38,7 +37,7 @@ export interface GeneratorContext {
|
||||
|
||||
export interface GenerationConfig {
|
||||
count: number;
|
||||
tone: ToneLevel;
|
||||
isExplicit: boolean;
|
||||
crudeLang: boolean;
|
||||
inspiration: string;
|
||||
}
|
||||
@@ -77,7 +76,7 @@ export function useAiGeneration(
|
||||
// Config state
|
||||
const [config, setConfig] = useState<GenerationConfig>({
|
||||
count: 10,
|
||||
tone: context?.isExplicit ? 5 : 2,
|
||||
isExplicit: Boolean(context?.isExplicit),
|
||||
crudeLang: false,
|
||||
inspiration: '',
|
||||
});
|
||||
@@ -118,14 +117,12 @@ export function useAiGeneration(
|
||||
// Council config
|
||||
const councilConfig = getCouncilConfig();
|
||||
|
||||
// Update tone when context changes
|
||||
// Update explicit flag when context changes
|
||||
useEffect(() => {
|
||||
if (type === 'questions') {
|
||||
setConfig(prev => ({
|
||||
...prev,
|
||||
tone: context?.isExplicit ? 5 : 2,
|
||||
}));
|
||||
}
|
||||
setConfig(prev => ({
|
||||
...prev,
|
||||
isExplicit: Boolean(context?.isExplicit),
|
||||
}));
|
||||
}, [context?.isExplicit, type]);
|
||||
|
||||
// Config setters
|
||||
@@ -133,8 +130,8 @@ export function useAiGeneration(
|
||||
setConfig(prev => ({ ...prev, count }));
|
||||
}, []);
|
||||
|
||||
const setTone = useCallback((tone: ToneLevel) => {
|
||||
setConfig(prev => ({ ...prev, tone }));
|
||||
const setExplicit = useCallback((isExplicit: boolean) => {
|
||||
setConfig(prev => ({ ...prev, isExplicit }));
|
||||
}, []);
|
||||
|
||||
const setCrudeLang = useCallback((crudeLang: boolean) => {
|
||||
@@ -219,7 +216,7 @@ export function useAiGeneration(
|
||||
setFilter('all');
|
||||
setSourceModel(null);
|
||||
|
||||
const isExplicit = config.tone >= 4;
|
||||
const isExplicit = config.isExplicit;
|
||||
|
||||
try {
|
||||
if (type === 'pack') {
|
||||
@@ -253,7 +250,7 @@ export function useAiGeneration(
|
||||
context.packName,
|
||||
config.count,
|
||||
undefined,
|
||||
config.tone,
|
||||
isExplicit ? 5 : 2,
|
||||
context.packDescription || undefined,
|
||||
context.existingQuestions,
|
||||
config.crudeLang,
|
||||
@@ -369,7 +366,7 @@ export function useAiGeneration(
|
||||
// Config
|
||||
config,
|
||||
setCount,
|
||||
setTone,
|
||||
setExplicit,
|
||||
setCrudeLang,
|
||||
setInspiration,
|
||||
|
||||
|
||||
@@ -203,11 +203,12 @@ export function PackFormDialog({
|
||||
<Switch
|
||||
checked={formData.is_public}
|
||||
onCheckedChange={(checked) => {
|
||||
setField('is_public', checked);
|
||||
// Clear scheduled release if making public immediately
|
||||
if (checked) {
|
||||
setField('scheduled_release_at', null);
|
||||
}
|
||||
const next = {
|
||||
...formData,
|
||||
is_public: checked,
|
||||
...(checked ? { scheduled_release_at: null } : {}),
|
||||
};
|
||||
onFormChange(next);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user