Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions api-docs/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -4363,6 +4363,9 @@ const docTemplate = `{
"description": {
"type": "string"
},
"include_speaker_info": {
"type": "boolean"
},
"model": {
"type": "string",
"minLength": 1
Expand Down Expand Up @@ -4581,6 +4584,9 @@ const docTemplate = `{
"id": {
"type": "string"
},
"include_speaker_info": {
"type": "boolean"
},
"model": {
"type": "string"
},
Expand Down
6 changes: 6 additions & 0 deletions api-docs/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -4357,6 +4357,9 @@
"description": {
"type": "string"
},
"include_speaker_info": {
"type": "boolean"
},
"model": {
"type": "string",
"minLength": 1
Expand Down Expand Up @@ -4575,6 +4578,9 @@
"id": {
"type": "string"
},
"include_speaker_info": {
"type": "boolean"
},
"model": {
"type": "string"
},
Expand Down
4 changes: 4 additions & 0 deletions api-docs/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,8 @@ definitions:
properties:
description:
type: string
include_speaker_info:
type: boolean
model:
minLength: 1
type: string
Expand Down Expand Up @@ -508,6 +510,8 @@ definitions:
type: string
id:
type: string
include_speaker_info:
type: boolean
model:
type: string
name:
Expand Down
15 changes: 11 additions & 4 deletions internal/api/summary_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ import (
)

type SummaryTemplateRequest struct {
Name string `json:"name" binding:"required,min=1"`
Description *string `json:"description"`
Model string `json:"model" binding:"required,min=1"`
Prompt string `json:"prompt" binding:"required,min=1"`
Name string `json:"name" binding:"required,min=1"`
Description *string `json:"description"`
Model string `json:"model" binding:"required,min=1"`
Prompt string `json:"prompt" binding:"required,min=1"`
IncludeSpeakerInfo *bool `json:"include_speaker_info"`
}

type SummarySettingsRequest struct {
Expand Down Expand Up @@ -73,6 +74,9 @@ func (h *Handler) CreateSummaryTemplate(c *gin.Context) {
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
if req.IncludeSpeakerInfo != nil {
item.IncludeSpeakerInfo = *req.IncludeSpeakerInfo
}
if err := h.summaryRepo.Create(c.Request.Context(), item); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create template"})
return
Expand Down Expand Up @@ -135,6 +139,9 @@ func (h *Handler) UpdateSummaryTemplate(c *gin.Context) {
item.Description = req.Description
item.Model = req.Model
item.Prompt = req.Prompt
if req.IncludeSpeakerInfo != nil {
item.IncludeSpeakerInfo = *req.IncludeSpeakerInfo
}
item.UpdatedAt = time.Now()
if err := h.summaryRepo.Update(c.Request.Context(), item); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update template"})
Expand Down
15 changes: 8 additions & 7 deletions internal/models/summary.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@ import (

// SummaryTemplate represents a saved summarization prompt/template
type SummaryTemplate struct {
ID string `json:"id" gorm:"primaryKey;type:varchar(36)"`
Name string `json:"name" gorm:"type:varchar(255);not null"`
Description *string `json:"description,omitempty" gorm:"type:text"`
Model string `json:"model" gorm:"type:varchar(255);not null;default:''"`
Prompt string `json:"prompt" gorm:"type:text;not null"`
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
ID string `json:"id" gorm:"primaryKey;type:varchar(36)"`
Name string `json:"name" gorm:"type:varchar(255);not null"`
Description *string `json:"description,omitempty" gorm:"type:text"`
Model string `json:"model" gorm:"type:varchar(255);not null;default:''"`
Prompt string `json:"prompt" gorm:"type:text;not null"`
IncludeSpeakerInfo bool `json:"include_speaker_info" gorm:"default:false"`
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
}

func (st *SummaryTemplate) BeforeCreate(tx *gorm.DB) error {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { Button } from "@/components/ui/button";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import { useAuth } from "@/features/auth/hooks/useAuth";
import { FormField } from "@/components/transcription/FormHelpers";
import { Loader2 } from "lucide-react";
Expand All @@ -14,6 +15,7 @@ export interface SummaryTemplate {
description?: string;
model?: string;
prompt: string;
include_speaker_info?: boolean;
created_at?: string;
updated_at?: string;
}
Expand Down Expand Up @@ -54,6 +56,7 @@ export function SummaryTemplateDialog({ open, onOpenChange, onSave, initial }: S
const [description, setDescription] = useState("");
const [model, setModel] = useState("");
const [prompt, setPrompt] = useState("");
const [includeSpeakerInfo, setIncludeSpeakerInfo] = useState(false);
const [saving, setSaving] = useState(false);
const [models, setModels] = useState<string[]>([]);
const { getAuthHeaders } = useAuth();
Expand All @@ -64,6 +67,7 @@ export function SummaryTemplateDialog({ open, onOpenChange, onSave, initial }: S
setDescription(initial?.description || "");
setModel(initial?.model || "");
setPrompt(initial?.prompt || "");
setIncludeSpeakerInfo(initial?.include_speaker_info || false);
// Load models when dialog opens
(async () => {
try {
Expand All @@ -89,7 +93,8 @@ export function SummaryTemplateDialog({ open, onOpenChange, onSave, initial }: S
name: name.trim(),
description: description.trim() || undefined,
model: model.trim(),
prompt: prompt.trim()
prompt: prompt.trim(),
include_speaker_info: includeSpeakerInfo
});
onOpenChange(false);
} finally {
Expand Down Expand Up @@ -176,6 +181,17 @@ Example:
Summarize the following transcript into concise bullet points. Focus on key decisions, action items, and important discussion topics."
/>
</FormField>

{/* Include Speaker Info Toggle */}
<FormField
label="Include Speaker Identification"
description="When enabled, speaker labels will be included in the transcript sent to the model. Useful for meeting notes and multi-speaker content."
>
<Switch
checked={includeSpeakerInfo}
onCheckedChange={setIncludeSpeakerInfo}
/>
</FormField>
</div>

{/* Footer */}
Expand Down
4 changes: 2 additions & 2 deletions web/frontend/src/features/settings/pages/SettingsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -173,9 +173,9 @@ export function Settings() {
const headers: HeadersInit = { 'Content-Type': 'application/json', ...getAuthHeaders() };
try {
if (tpl.id) {
await fetch(`/api/v1/summaries/${tpl.id}`, { method: 'PUT', headers, body: JSON.stringify({ name: tpl.name, description: tpl.description, model: tpl.model, prompt: tpl.prompt }) });
await fetch(`/api/v1/summaries/${tpl.id}`, { method: 'PUT', headers, body: JSON.stringify({ name: tpl.name, description: tpl.description, model: tpl.model, prompt: tpl.prompt, include_speaker_info: tpl.include_speaker_info }) });
} else {
await fetch('/api/v1/summaries', { method: 'POST', headers, body: JSON.stringify({ name: tpl.name, description: tpl.description, model: tpl.model, prompt: tpl.prompt }) });
await fetch('/api/v1/summaries', { method: 'POST', headers, body: JSON.stringify({ name: tpl.name, description: tpl.description, model: tpl.model, prompt: tpl.prompt, include_speaker_info: tpl.include_speaker_info }) });
}
} finally {
// Invalidate cache to propagate changes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,28 @@ import rehypeKatex from 'rehype-katex';
import rehypeHighlight from 'rehype-highlight';
import { useSummaryTemplates, useSummarizer, useExistingSummary } from "@/features/transcription/hooks/useTranscriptionSummary";

import { useTranscript, useAudioDetail } from "@/features/transcription/hooks/useAudioDetail";
import { useTranscript, useAudioDetail, type Transcript } from "@/features/transcription/hooks/useAudioDetail";
import { useSpeakerMappings } from "@/features/transcription/hooks/useTranscriptionSpeakers";

import { Sparkles, Download, Copy, RefreshCw, ChevronDown, FileText } from "lucide-react";

// Helper function to format transcript with speaker labels
function formatTranscriptWithSpeakers(
transcript: Transcript,
speakerMappings: Record<string, string>
): string {
// If no segments, fall back to plain text
if (!transcript.segments || transcript.segments.length === 0) {
return transcript.text || '';
}

// Format each segment with speaker label
return transcript.segments.map(segment => {
const speaker = speakerMappings[segment.speaker || ''] || segment.speaker || 'UNKNOWN';
return `[${speaker}] ${segment.text.trim()}`;
}).join('\n');
}

interface SummaryDialogProps {
audioId: string;
isOpen: boolean;
Expand All @@ -46,6 +64,7 @@ export function SummaryDialog({ audioId, isOpen, onClose, llmReady }: SummaryDia
const { data: existingSummary, isLoading: summaryLoading } = useExistingSummary(audioId);
const { data: transcript } = useTranscript(audioId, true);
const { data: audioFile } = useAudioDetail(audioId);
const { data: speakerMappings = {} } = useSpeakerMappings(audioId, true);

// Hooks
const { generateSummary, isStreaming, streamContent, error } = useSummarizer(audioId);
Expand Down Expand Up @@ -77,8 +96,12 @@ export function SummaryDialog({ audioId, isOpen, onClose, llmReady }: SummaryDia
if (!selectedTemplate || !transcript) return;

setShowOutput(true);
const transcriptText = transcript.text || '';
generateSummary(selectedTemplate.id, selectedTemplate.model, selectedTemplate.prompt, transcriptText);
// Use formatted transcript with speaker info if template has it enabled
const includeSpeakers = selectedTemplate.include_speaker_info ?? false;
const transcriptText = includeSpeakers
? formatTranscriptWithSpeakers(transcript, speakerMappings)
: (transcript.text || '');
generateSummary(selectedTemplate.id, selectedTemplate.model, selectedTemplate.prompt, transcriptText, includeSpeakers);
};

const handleCopy = async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export interface SummaryTemplate {
name: string;
model: string;
prompt: string;
include_speaker_info?: boolean;
}

export function useSummaryTemplates() {
Expand Down Expand Up @@ -46,12 +47,15 @@ export function useSummarizer(audioId: string) {
const [streamContent, setStreamContent] = useState("");
const [error, setError] = useState<string | null>(null);

const generateSummary = async (templateId: string, model: string, prompt: string, transcriptText: string) => {
const generateSummary = async (templateId: string, model: string, prompt: string, transcriptText: string, includeSpeakerInfo?: boolean) => {
setIsStreaming(true);
setStreamContent("");
setError(null);

const combinedContent = `Transcript:\n${transcriptText}\n\nInstructions:\n${prompt}`;
const transcriptLabel = includeSpeakerInfo
? 'Transcript (with speaker labels - each line is prefixed with [SPEAKER_NAME]):'
: 'Transcript:';
const combinedContent = `${transcriptLabel}\n${transcriptText}\n\nInstructions:\n${prompt}`;

try {
const res = await fetch('/api/v1/summarize', {
Expand Down
6 changes: 6 additions & 0 deletions web/project-site/public/api/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -4357,6 +4357,9 @@
"description": {
"type": "string"
},
"include_speaker_info": {
"type": "boolean"
},
"model": {
"type": "string",
"minLength": 1
Expand Down Expand Up @@ -4575,6 +4578,9 @@
"id": {
"type": "string"
},
"include_speaker_info": {
"type": "boolean"
},
"model": {
"type": "string"
},
Expand Down
Loading