Skip to content

Commit dcd96be

Browse files
committed
feat: add exordio for Introductor, role-specific prompts, global language setting
- Remove language field from GenerateInputSchema and FactoidsInputSchema - Add ExordioSchema (hook + transition) to GenerateOutputSchema - Remove per-feature language dropdowns from GenerateForm and FactoidsForm - Rewrite GENERATE_PROMPT with detailed role-specific instructions - Add exordio display section to GenerateResults - Update client.ts to inject global language and handle exordio transform - Update prompt-builder to read language from injected field - Fix all tests to match new schemas
1 parent abd260a commit dcd96be

11 files changed

Lines changed: 134 additions & 73 deletions

File tree

‎public/robots.txt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,4 @@ Allow: /
1313
User-agent: *
1414
Allow: /
1515

16-
Sitemap: https://debate.pi-stack.org/sitemap.xml
16+
Sitemap: https://debate.pi-stack.org/sitemap.xml

‎src/components/features/factoids/FactoidsForm.tsx‎

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import {
1515
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
1616
import { FactoidsInputSchema, type FactoidsInput } from '@/lib/ai/schemas/factoids';
1717
import { LoadingSpinner } from '@/components/ui/loading-spinner';
18-
import { useSettingsStore } from '@/lib/stores/settings-store';
1918

2019
import { useSessionStore } from '@/lib/stores/session-store';
2120
import { useEffect } from 'react';
@@ -26,7 +25,6 @@ interface FactoidsFormProps {
2625
}
2726

2827
export function FactoidsForm({ onSubmit, isLoading }: FactoidsFormProps) {
29-
const globalLanguage = useSettingsStore((s) => s.language);
3028
const { factoidsForm, setFactoidsForm } = useSessionStore();
3129

3230
const { register, handleSubmit, setValue, watch, formState: { errors } } = useForm<FactoidsInput>({
@@ -36,7 +34,6 @@ export function FactoidsForm({ onSubmit, isLoading }: FactoidsFormProps) {
3634
region: '',
3735
timeframe: '',
3836
sourceType: 'any',
39-
language: globalLanguage,
4037
...factoidsForm, // Override with persisted state
4138
},
4239
});
@@ -125,23 +122,6 @@ export function FactoidsForm({ onSubmit, isLoading }: FactoidsFormProps) {
125122
</SelectContent>
126123
</Select>
127124
</div>
128-
129-
{/* Language */}
130-
<div className="space-y-2">
131-
<Label className="text-sm font-medium">Language</Label>
132-
<Select
133-
value={currentValues.language}
134-
onValueChange={(value) => setValue('language', value as FactoidsInput['language'])}
135-
>
136-
<SelectTrigger>
137-
<SelectValue />
138-
</SelectTrigger>
139-
<SelectContent>
140-
<SelectItem value="en">English</SelectItem>
141-
<SelectItem value="es">Español</SelectItem>
142-
</SelectContent>
143-
</Select>
144-
</div>
145125
</div>
146126

147127

‎src/components/features/generate/GenerateForm.tsx‎

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import {
1616
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
1717
import { GenerateInputSchema, type GenerateInput } from '@/lib/ai/schemas/generate';
1818
import { LoadingSpinner } from '@/components/ui/loading-spinner';
19-
import { useSettingsStore } from '@/lib/stores/settings-store';
2019
import { useSessionStore } from '@/lib/stores/session-store';
2120
import { useSpeechToText } from '@/hooks/use-speech-to-text';
2221

@@ -27,7 +26,6 @@ interface GenerateFormProps {
2726
}
2827

2928
export function GenerateForm({ onSubmit, isLoading }: GenerateFormProps) {
30-
const globalLanguage = useSettingsStore((s) => s.language);
3129
const { generateForm, setGenerateForm } = useSessionStore();
3230

3331
const { register, handleSubmit, setValue, watch, formState: { errors } } = useForm<GenerateInput>({
@@ -40,7 +38,6 @@ export function GenerateForm({ onSubmit, isLoading }: GenerateFormProps) {
4038
length: 'standard',
4139
audience: 'intermediate',
4240
tone: 'formal',
43-
language: globalLanguage,
4441
...generateForm, // Override defaults with persisted state
4542
},
4643
});
@@ -238,23 +235,6 @@ export function GenerateForm({ onSubmit, isLoading }: GenerateFormProps) {
238235
</SelectContent>
239236
</Select>
240237
</div>
241-
242-
{/* Language */}
243-
<div className="space-y-2">
244-
<Label className="text-sm font-medium">Language</Label>
245-
<Select
246-
value={currentValues.language}
247-
onValueChange={(value) => setValue('language', value as GenerateInput['language'])}
248-
>
249-
<SelectTrigger>
250-
<SelectValue />
251-
</SelectTrigger>
252-
<SelectContent>
253-
<SelectItem value="en">English</SelectItem>
254-
<SelectItem value="es">Español</SelectItem>
255-
</SelectContent>
256-
</Select>
257-
</div>
258238
</div>
259239

260240

‎src/components/features/generate/GenerateResults.tsx‎

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useState } from 'react';
2-
import { Target, Shield, Swords, AlertTriangle, MessageSquare, FileDown, FileText, Save, GitBranch, FileUp, Loader2 } from 'lucide-react';
2+
import { Target, Shield, Swords, AlertTriangle, MessageSquare, FileDown, FileText, Save, GitBranch, FileUp, Loader2, Sparkles } from 'lucide-react';
33
import { Badge } from '@/components/ui/badge';
44
import { Button } from '@/components/ui/button';
55
import { SectionCard } from '@/components/ui/section-card';
@@ -154,6 +154,28 @@ export function GenerateResults({ result, onRegenerateSection, onDiscuss, onSave
154154
</div>
155155
</div>
156156

157+
{/* Exordio (Introductor only) */}
158+
{result.exordio && (result.exordio.hook || result.exordio.transition) && (
159+
<SectionCard
160+
title="Exordio"
161+
description="Your compelling opening"
162+
icon={<Sparkles className="h-5 w-5" />}
163+
onRegenerate={() => onRegenerateSection?.('exordio')}
164+
>
165+
<div className="space-y-4">
166+
<div>
167+
<h4 className="text-sm font-medium text-muted-foreground mb-1">Hook</h4>
168+
<p className="text-foreground italic">{result.exordio.hook}</p>
169+
</div>
170+
<Separator />
171+
<div>
172+
<h4 className="text-sm font-medium text-muted-foreground mb-1">Transition</h4>
173+
<p className="text-foreground">{result.exordio.transition}</p>
174+
</div>
175+
</div>
176+
</SectionCard>
177+
)}
178+
157179
{/* Case Structure */}
158180
<SectionCard
159181
title="Case Structure"

‎src/lib/ai/client.ts‎

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -430,9 +430,18 @@ function parseSupabaseError(
430430
* This handles cases where the AI returns a different structure than requested.
431431
*/
432432
function transformGenerateResponse(response: Record<string, unknown>): GenerateOutput {
433-
// If already in correct format, return as-is
433+
// If already in correct format, return as-is (with exordio preserved)
434434
if (response.caseStructure && response.contentions && response.predictedOpponentCase && response.counters && response.metadata) {
435-
return response as unknown as GenerateOutput;
435+
// Ensure exordio is properly structured if present
436+
const result = response as unknown as GenerateOutput;
437+
if (response.exordio && typeof response.exordio === 'object') {
438+
const ex = response.exordio as Record<string, unknown>;
439+
result.exordio = {
440+
hook: String(ex.hook || ''),
441+
transition: String(ex.transition || ''),
442+
};
443+
}
444+
return result;
436445
}
437446

438447
// Transform from wrong format to correct format
@@ -467,7 +476,7 @@ function transformGenerateResponse(response: Record<string, unknown>): GenerateO
467476
// Transform potentialWeaknesses to predictedOpponentCase
468477
const weaknesses = (response.potentialWeaknesses as string[]) || [];
469478

470-
return {
479+
const result: GenerateOutput = {
471480
caseStructure: {
472481
thesis: (response.thesis as string) || (response.title as string) || '',
473482
framework: 'General framework',
@@ -499,6 +508,17 @@ function transformGenerateResponse(response: Record<string, unknown>): GenerateO
499508
generatedAt: new Date().toISOString(),
500509
},
501510
};
511+
512+
// Preserve exordio if present in the original response
513+
if (response.exordio && typeof response.exordio === 'object') {
514+
const ex = response.exordio as Record<string, unknown>;
515+
result.exordio = {
516+
hook: String(ex.hook || ''),
517+
transition: String(ex.transition || ''),
518+
};
519+
}
520+
521+
return result;
502522
}
503523

504524
/**
@@ -586,7 +606,10 @@ function transformBrainstormResponse(response: Record<string, unknown>): Brainst
586606

587607
export async function generateCase(input: GenerateInput, config: ProviderConfig): Promise<GenerateOutput> {
588608
if (config.provider === 'mock') return mockGenerateCase(input.motion, input.side);
589-
const data = await callSupabaseAI('generate', input as Record<string, unknown>, config) as { output: unknown; discoveredSources?: { title: string; url: string; content: string }[] };
609+
// Inject global language into input for the edge function
610+
const language = useSettingsStore.getState().language;
611+
const inputWithLanguage = { ...input, language } as Record<string, unknown>;
612+
const data = await callSupabaseAI('generate', inputWithLanguage, config) as { output: unknown; discoveredSources?: { title: string; url: string; content: string }[] };
590613

591614
// Auto-save discovered sources to library
592615
if (data.discoveredSources && data.discoveredSources.length > 0) {
@@ -634,7 +657,10 @@ export async function brainstorm(input: BrainstormInput, config: ProviderConfig)
634657

635658
export async function factoids(input: FactoidsInput, config: ProviderConfig): Promise<FactoidsOutput> {
636659
if (config.provider === 'mock') return mockFactoids(input.claim);
637-
const data = await callSupabaseAI('factoids', input as Record<string, unknown>, config) as { output: unknown };
660+
// Inject global language into input for the edge function
661+
const language = useSettingsStore.getState().language;
662+
const inputWithLanguage = { ...input, language } as Record<string, unknown>;
663+
const data = await callSupabaseAI('factoids', inputWithLanguage, config) as { output: unknown };
638664
return data.output as FactoidsOutput;
639665
}
640666

‎src/lib/ai/schemas/factoids.ts‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ export const FactoidsInputSchema = z.object({
66
region: z.string().min(1, 'Region is required'),
77
sourceType: z.enum(['academic', 'government', 'news', 'any']),
88
timeframe: z.string().optional(),
9-
language: z.enum(['en', 'es']),
109
});
1110

1211
export type FactoidsInput = z.infer<typeof FactoidsInputSchema>;

‎src/lib/ai/schemas/generate.ts‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ export const GenerateInputSchema = z.object({
99
length: z.enum(['brief', 'standard', 'comprehensive']),
1010
audience: z.enum(['beginner', 'intermediate', 'advanced']),
1111
tone: z.enum(['formal', 'conversational', 'aggressive']),
12-
language: z.enum(['en', 'es']),
1312
});
1413

1514
export type GenerateInput = z.infer<typeof GenerateInputSchema>;
@@ -24,7 +23,13 @@ export const ContentionSchema = z.object({
2423
impact: z.string(),
2524
});
2625

26+
export const ExordioSchema = z.object({
27+
hook: z.string(),
28+
transition: z.string(),
29+
});
30+
2731
export const GenerateOutputSchema = z.object({
32+
exordio: ExordioSchema.optional(),
2833
caseStructure: z.object({
2934
thesis: z.string(),
3035
framework: z.string(),

‎src/test/ai/client.test.ts‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ vi.mock('@/lib/stores/settings-store', () => ({
2222
useSettingsStore: {
2323
getState: vi.fn(() => ({
2424
customSystemPrompt: 'test-system-prompt',
25+
language: 'en',
2526
})),
2627
},
2728
}));
@@ -76,7 +77,6 @@ describe('AI Client Integration', () => {
7677
length: 'brief',
7778
audience: 'advanced',
7879
tone: 'formal',
79-
language: 'en'
8080
};
8181

8282
await generateCase(input, mockConfig);
@@ -89,7 +89,7 @@ describe('AI Client Integration', () => {
8989
provider: 'openai',
9090
model: 'gpt-4o',
9191
temperature: 0.8,
92-
input: input,
92+
input: expect.objectContaining(input),
9393
customSystemPrompt: 'test-system-prompt',
9494
sources: expect.arrayContaining([
9595
expect.objectContaining({ title: 'Source 1' }) // Only active source
@@ -256,7 +256,6 @@ describe('AI Client Integration', () => {
256256
region: 'United States',
257257
sourceType: 'government',
258258
timeframe: '2020-2025',
259-
language: 'en',
260259
}, mockConfig);
261260

262261
expect(supabaseMock.functions.invoke).toHaveBeenCalledWith('ai', expect.objectContaining({

‎src/test/schemas.test.ts‎

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ describe('Generate Schema', () => {
2121
length: 'standard',
2222
audience: 'intermediate',
2323
tone: 'formal',
24-
language: 'en',
2524
};
2625

2726
it('accepts valid input', () => {
@@ -63,11 +62,6 @@ describe('Generate Schema', () => {
6362
const result = GenerateInputSchema.safeParse({ ...validInput, tone: 'invalid' });
6463
expect(result.success).toBe(false);
6564
});
66-
67-
it('rejects invalid language', () => {
68-
const result = GenerateInputSchema.safeParse({ ...validInput, language: 'fr' });
69-
expect(result.success).toBe(false);
70-
});
7165
});
7266

7367
describe('ContentionSchema', () => {
@@ -425,7 +419,6 @@ describe('Factoids Schema', () => {
425419
region: 'United States',
426420
sourceType: 'academic',
427421
timeframe: '2020-2024',
428-
language: 'en',
429422
};
430423

431424
it('accepts valid input', () => {

0 commit comments

Comments
 (0)