Detail: personality picker beside Re-summarize
Chips: Neutral / Sarcastic / Funny / Dry wit. The chosen voice rides
the reprocess call and the button echoes it ('Re-summarize · Sarcastic');
an existing summary shows '· in a sarcastic voice' next to its heading.
compileKotlin + 39 unit tests green.
This commit is contained in:
parent
b63f49241d
commit
994dbc3690
4 changed files with 61 additions and 10 deletions
|
|
@ -1154,7 +1154,8 @@ class DesktopState(private val appDir: File = defaultAppDir()) {
|
|||
* endpoint as the upload path. [model] is sent for "transcribe" so a
|
||||
* model override switch persists on the server row.
|
||||
*/
|
||||
fun reprocessInPlace(d0: DetailUi, job: String, model: String? = null) {
|
||||
fun reprocessInPlace(d0: DetailUi, job: String, model: String? = null,
|
||||
tone: String? = null) {
|
||||
val remoteId = d0.remoteId ?: return
|
||||
pollJob?.cancel()
|
||||
pollJob = scope.launch {
|
||||
|
|
@ -1164,7 +1165,7 @@ class DesktopState(private val appDir: File = defaultAppDir()) {
|
|||
d0.uploadProgress = null
|
||||
d0.error = null
|
||||
_detail.value = d0.copy()
|
||||
runCatching { provider.reprocess(remoteId, job, model) }.onFailure {
|
||||
runCatching { provider.reprocess(remoteId, job, model, tone) }.onFailure {
|
||||
if (job == "transcribe" && it is ProviderError.NotFound) {
|
||||
// Mapping points at a deleted recording: drop it so the
|
||||
// next attempt re-uploads instead of failing forever.
|
||||
|
|
@ -1223,12 +1224,13 @@ class DesktopState(private val appDir: File = defaultAppDir()) {
|
|||
}
|
||||
|
||||
/** "Summarize" button: regenerate the summary in place from the
|
||||
* current transcript. Requires a known server recording. */
|
||||
fun summarize() {
|
||||
* current transcript. Requires a known server recording.
|
||||
* [tone] (e.g. "sarcastic") restyles the voice; null = neutral. */
|
||||
fun summarize(tone: String? = null) {
|
||||
val d = _detail.value ?: return
|
||||
val remoteId = d.remoteId ?: loadMapping(d.file)?.recordingId ?: return
|
||||
d.remoteId = remoteId
|
||||
reprocessInPlace(d, job = "summarize")
|
||||
reprocessInPlace(d, job = "summarize", tone = tone)
|
||||
}
|
||||
|
||||
/** Server recording id for a library file, if it was ever uploaded. */
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import androidx.compose.material3.ButtonDefaults
|
|||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.FilterChip
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
|
|
@ -514,6 +515,16 @@ fun LibraryScreen(state: DesktopState) {
|
|||
|
||||
// ---- detail -----------------------------------------------------------------
|
||||
|
||||
/** Personalities offered beside Re-summarize: (label, tone sent to the
|
||||
* engine). The tone string is interpolated into "in a <tone> tone of
|
||||
* voice", so it must read naturally there. null = neutral house voice. */
|
||||
private val SUMMARIZE_PERSONAS = listOf(
|
||||
"Neutral" to null,
|
||||
"Sarcastic" to "sarcastic",
|
||||
"Funny" to "funny",
|
||||
"Dry wit" to "dry, witty",
|
||||
)
|
||||
|
||||
@androidx.compose.runtime.Composable
|
||||
fun DetailScreen(state: DesktopState) {
|
||||
val detail = state.detail.collectAsState().value ?: run {
|
||||
|
|
@ -525,6 +536,8 @@ fun DetailScreen(state: DesktopState) {
|
|||
var confirmDelete by remember { mutableStateOf(false) }
|
||||
var deleteError by remember { mutableStateOf<String?>(null) }
|
||||
var recQuery by remember { mutableStateOf("") }
|
||||
// Voice for the next summarize: null = Neutral (server default).
|
||||
var sumTone by remember(detail.file.name) { mutableStateOf<String?>(null) }
|
||||
val playingFile by state.playingFile.collectAsState()
|
||||
val playError by state.playError.collectAsState()
|
||||
val isPlaying = playingFile?.absolutePath == detail.file.absolutePath
|
||||
|
|
@ -718,7 +731,16 @@ fun DetailScreen(state: DesktopState) {
|
|||
}
|
||||
detail.summary?.let { s ->
|
||||
if (s.short.isNotBlank()) {
|
||||
Text("Summary", style = MaterialTheme.typography.titleMedium)
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text("Summary", style = MaterialTheme.typography.titleMedium)
|
||||
// Honest tag: which voice this summary was written in.
|
||||
s.tone?.let {
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text("· in a $it voice",
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.primary)
|
||||
}
|
||||
}
|
||||
Text(s.short)
|
||||
}
|
||||
}
|
||||
|
|
@ -748,13 +770,32 @@ fun DetailScreen(state: DesktopState) {
|
|||
val hasSummary = detail.summary != null ||
|
||||
(report?.contains("## Summary") == true)
|
||||
val remoteKnown = state.remoteIdFor(detail.file) != null
|
||||
// Personality for the next summary: pick a voice, then hit
|
||||
// Re-summarize. Neutral = the plain house voice.
|
||||
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
Text("Summarize in the voice of",
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
SUMMARIZE_PERSONAS.forEach { (label, tone) ->
|
||||
FilterChip(
|
||||
selected = sumTone == tone,
|
||||
onClick = { sumTone = tone },
|
||||
label = { Text(label) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
TextButton({ state.transcribe() }) {
|
||||
Text(if (hasTranscript) "Re-transcribe" else "Transcribe")
|
||||
}
|
||||
TextButton({ state.summarize() },
|
||||
TextButton({ state.summarize(sumTone) },
|
||||
enabled = remoteKnown && hasTranscript) {
|
||||
Text(if (hasSummary) "Re-summarize" else "Summarize")
|
||||
Text(buildString {
|
||||
append(if (hasSummary) "Re-summarize" else "Summarize")
|
||||
sumTone?.let { append(" · $it") }
|
||||
})
|
||||
}
|
||||
}
|
||||
if (!remoteKnown || !hasTranscript) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue