Detail: summary paragraph, voice chips, and copy actions consolidated into one card
This commit is contained in:
parent
a6e1d30f92
commit
90f6f1b786
1 changed files with 111 additions and 71 deletions
|
|
@ -913,87 +913,126 @@ fun DetailScreen(state: DesktopState) {
|
||||||
// is already inside the report text shown below.
|
// is already inside the report text shown below.
|
||||||
if (detail.transcript != null) detail.summary?.let { s ->
|
if (detail.transcript != null) detail.summary?.let { s ->
|
||||||
if (s.short.isNotBlank()) {
|
if (s.short.isNotBlank()) {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
// The summary owns an elevated surface: paragraph, voice
|
||||||
Text("Summary", style = MaterialTheme.typography.titleMedium)
|
// controls, and copy live in ONE card, so the chips read
|
||||||
// Honest tag: which voice this summary was written in
|
// as controls ON this text instead of a loose row between
|
||||||
// — always shown; no persona = the neutral house voice.
|
// content blocks (the "tone controls feel disconnected"
|
||||||
Spacer(Modifier.width(8.dp))
|
// complaint). Paragraph, chips, copy — top to bottom, the
|
||||||
Text("· in a ${s.tone ?: "neutral"} voice",
|
// actual order of the usage loop.
|
||||||
style = MaterialTheme.typography.labelMedium,
|
|
||||||
color = MaterialTheme.colorScheme.primary)
|
|
||||||
}
|
|
||||||
Text(s.short)
|
|
||||||
// One-click copy: the summary text and the action items
|
|
||||||
// are what get pasted into notes/messages — grabbing them
|
|
||||||
// by mouse-selecting a wall of text was the old way.
|
|
||||||
val clipboard = LocalClipboardManager.current
|
val clipboard = LocalClipboardManager.current
|
||||||
var copiedNote by remember(detail.file.name) {
|
SonarCard(modifier = Modifier.fillMaxWidth()) {
|
||||||
mutableStateOf<String?>(null)
|
Column(
|
||||||
}
|
modifier = Modifier.fillMaxWidth()
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(Shonar.Space2)) {
|
.padding(Shonar.CardPadding),
|
||||||
ShonarGhostButton({
|
verticalArrangement = Arrangement.spacedBy(Shonar.Space3),
|
||||||
clipboard.setText(AnnotatedString(s.short))
|
) {
|
||||||
copiedNote = "Summary copied"
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
}) { Text("Copy summary") }
|
Text("Summary",
|
||||||
val actionItems = s.list("action_items")
|
style = MaterialTheme.typography.titleMedium)
|
||||||
if (actionItems.isNotEmpty()) {
|
// Honest tag: which voice this summary was written in
|
||||||
ShonarGhostButton({
|
// — always shown; no persona = the neutral house voice.
|
||||||
clipboard.setText(AnnotatedString(
|
Spacer(Modifier.width(Shonar.Space2))
|
||||||
actionItems.joinToString("\n") { "- $it" }))
|
Text("· in a ${s.tone ?: "neutral"} voice",
|
||||||
copiedNote = "Action items copied"
|
style = MaterialTheme.typography.labelMedium,
|
||||||
}) { Text("Copy action items") }
|
color = MaterialTheme.colorScheme.primary)
|
||||||
}
|
}
|
||||||
}
|
Text(s.short, style = MaterialTheme.typography.bodyLarge)
|
||||||
copiedNote?.let {
|
// Voice controls directly under the paragraph —
|
||||||
Text(it, style = MaterialTheme.typography.labelMedium,
|
// they act on this summary and the shared card
|
||||||
color = MaterialTheme.colorScheme.primary)
|
// says so. Every time they sat further down
|
||||||
}
|
// (page bottom, below the bullets) they fell
|
||||||
// Voice controls FIRST under the summary — before the long
|
// off-screen and read as "missing".
|
||||||
// sections. Every time they sat further down (page bottom,
|
run {
|
||||||
// then below the bullets) they fell off-screen and read as
|
val report = detail.reportText
|
||||||
// "missing". They act on this summary; they live here.
|
val hasTranscript = detail.transcript != null ||
|
||||||
run {
|
(report != null &&
|
||||||
val report = detail.reportText
|
!report.contains("_No transcript available._"))
|
||||||
val hasTranscript = detail.transcript != null ||
|
val remoteKnown = state.remoteIdFor(detail.file) != null
|
||||||
(report != null && !report.contains("_No transcript available._"))
|
val sumBusy = detail.jobs.firstOrNull { it.jobType == "summarize" }
|
||||||
val remoteKnown = state.remoteIdFor(detail.file) != null
|
?.status in setOf("queued", "running")
|
||||||
val sumBusy = detail.jobs.firstOrNull { it.jobType == "summarize" }
|
val voicesEnabled = remoteKnown && hasTranscript && !sumBusy
|
||||||
?.status in setOf("queued", "running")
|
Column(verticalArrangement = Arrangement.spacedBy(Shonar.Space2)) {
|
||||||
val voicesEnabled = remoteKnown && hasTranscript && !sumBusy
|
Text("Re-summarize in the voice of",
|
||||||
Column(verticalArrangement = Arrangement.spacedBy(Shonar.Space1)) {
|
style = MaterialTheme.typography.labelMedium,
|
||||||
Text("Re-summarize in the voice of",
|
color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||||
style = MaterialTheme.typography.labelMedium,
|
Row(horizontalArrangement = Arrangement.spacedBy(Shonar.Space2)) {
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant)
|
SUMMARIZE_PERSONAS.forEach { (label, tone) ->
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(Shonar.Space2)) {
|
PillChip(
|
||||||
SUMMARIZE_PERSONAS.forEach { (label, tone) ->
|
selected = chipTone == tone,
|
||||||
PillChip(
|
enabled = voicesEnabled,
|
||||||
selected = chipTone == tone,
|
onClick = {
|
||||||
enabled = voicesEnabled,
|
sumTone = tone
|
||||||
onClick = {
|
state.summarize(tone)
|
||||||
sumTone = tone
|
},
|
||||||
state.summarize(tone)
|
label = label,
|
||||||
},
|
)
|
||||||
label = label,
|
}
|
||||||
)
|
}
|
||||||
|
if (!voicesEnabled && !remoteKnown) {
|
||||||
|
HelperText("Summarize unlocks once this file is uploaded (press Transcribe).")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!voicesEnabled && !remoteKnown) {
|
// One-click copy: what gets pasted into
|
||||||
HelperText("Summarize unlocks once this file is uploaded (press Transcribe).")
|
// notes/messages — mouse-selecting a wall of text
|
||||||
|
// was the old way.
|
||||||
|
var summaryCopied by remember(detail.file.name) {
|
||||||
|
mutableStateOf(false)
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(Shonar.Space2),
|
||||||
|
) {
|
||||||
|
ShonarGhostButton({
|
||||||
|
clipboard.setText(AnnotatedString(s.short))
|
||||||
|
summaryCopied = true
|
||||||
|
}) { Text(if (summaryCopied) "Summary copied" else "Copy summary") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Spacer(Modifier.height(4.dp))
|
Spacer(Modifier.height(Shonar.Space4))
|
||||||
// Full structured sections (key points, decisions, action
|
// Action items get their own card, first among the
|
||||||
// items, questions, details) right on screen — the saved
|
// sections: they are the content users actually carry out
|
||||||
// .md report had them but the live view showed only the
|
// of the app, and they were styled exactly like Questions.
|
||||||
|
val actionItems = s.list("action_items")
|
||||||
|
if (actionItems.isNotEmpty()) {
|
||||||
|
var itemsCopied by remember(detail.file.name) {
|
||||||
|
mutableStateOf(false)
|
||||||
|
}
|
||||||
|
SonarCard(modifier = Modifier.fillMaxWidth()) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
.padding(Shonar.CardPadding),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(Shonar.Space2),
|
||||||
|
) {
|
||||||
|
Text("Action items",
|
||||||
|
style = MaterialTheme.typography.titleMedium)
|
||||||
|
actionItems.forEach { item ->
|
||||||
|
Text("→ $item",
|
||||||
|
style = MaterialTheme.typography.bodyMedium)
|
||||||
|
}
|
||||||
|
ShonarOutlinedButton({
|
||||||
|
clipboard.setText(AnnotatedString(
|
||||||
|
actionItems.joinToString("\n") { "- $it" }))
|
||||||
|
itemsCopied = true
|
||||||
|
}) { Text(if (itemsCopied) "Copied" else "Copy action items") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Spacer(Modifier.height(Shonar.Space4))
|
||||||
|
}
|
||||||
|
// Remaining structured sections (key points, decisions,
|
||||||
|
// questions, details) right on screen — the saved .md
|
||||||
|
// report had them but the live view showed only the
|
||||||
// one-liner, so "what did I have to do?" required opening
|
// one-liner, so "what did I have to do?" required opening
|
||||||
// the file elsewhere.
|
// the file elsewhere. Section headings separate with
|
||||||
SUMMARY_LIST_KEYS.forEach { key ->
|
// Space4 (more above the heading than below it).
|
||||||
|
SUMMARY_LIST_KEYS.filter { it != "action_items" }.forEach { key ->
|
||||||
val items = s.list(key)
|
val items = s.list(key)
|
||||||
if (items.isNotEmpty()) {
|
if (items.isNotEmpty()) {
|
||||||
Spacer(Modifier.height(6.dp))
|
Spacer(Modifier.height(Shonar.Space4))
|
||||||
Text(key.replace('_', ' ').replaceFirstChar { it.uppercase() },
|
Text(key.replace('_', ' ').replaceFirstChar { it.uppercase() },
|
||||||
style = MaterialTheme.typography.titleSmall)
|
style = MaterialTheme.typography.titleSmall)
|
||||||
|
Spacer(Modifier.height(Shonar.Space1))
|
||||||
items.forEach { item ->
|
items.forEach { item ->
|
||||||
Text("• $item",
|
Text("• $item",
|
||||||
style = MaterialTheme.typography.bodyMedium)
|
style = MaterialTheme.typography.bodyMedium)
|
||||||
|
|
@ -1001,11 +1040,12 @@ fun DetailScreen(state: DesktopState) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (s.detailed.isNotBlank()) {
|
if (s.detailed.isNotBlank()) {
|
||||||
Spacer(Modifier.height(6.dp))
|
Spacer(Modifier.height(Shonar.Space4))
|
||||||
Text("Details", style = MaterialTheme.typography.titleSmall)
|
Text("Details", style = MaterialTheme.typography.titleSmall)
|
||||||
|
Spacer(Modifier.height(Shonar.Space1))
|
||||||
Text(s.detailed, style = MaterialTheme.typography.bodyMedium)
|
Text(s.detailed, style = MaterialTheme.typography.bodyMedium)
|
||||||
}
|
}
|
||||||
Spacer(Modifier.height(4.dp))
|
Spacer(Modifier.height(Shonar.Space4))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Re-transcribe + summarize progress (the chips themselves live
|
// Re-transcribe + summarize progress (the chips themselves live
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue