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,54 +913,45 @@ fun DetailScreen(state: DesktopState) {
|
|||
// is already inside the report text shown below.
|
||||
if (detail.transcript != null) detail.summary?.let { s ->
|
||||
if (s.short.isNotBlank()) {
|
||||
// The summary owns an elevated surface: paragraph, voice
|
||||
// controls, and copy live in ONE card, so the chips read
|
||||
// as controls ON this text instead of a loose row between
|
||||
// content blocks (the "tone controls feel disconnected"
|
||||
// complaint). Paragraph, chips, copy — top to bottom, the
|
||||
// actual order of the usage loop.
|
||||
val clipboard = LocalClipboardManager.current
|
||||
SonarCard(modifier = Modifier.fillMaxWidth()) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
.padding(Shonar.CardPadding),
|
||||
verticalArrangement = Arrangement.spacedBy(Shonar.Space3),
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text("Summary", style = MaterialTheme.typography.titleMedium)
|
||||
Text("Summary",
|
||||
style = MaterialTheme.typography.titleMedium)
|
||||
// Honest tag: which voice this summary was written in
|
||||
// — always shown; no persona = the neutral house voice.
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Spacer(Modifier.width(Shonar.Space2))
|
||||
Text("· in a ${s.tone ?: "neutral"} voice",
|
||||
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
|
||||
var copiedNote by remember(detail.file.name) {
|
||||
mutableStateOf<String?>(null)
|
||||
}
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(Shonar.Space2)) {
|
||||
ShonarGhostButton({
|
||||
clipboard.setText(AnnotatedString(s.short))
|
||||
copiedNote = "Summary copied"
|
||||
}) { Text("Copy summary") }
|
||||
val actionItems = s.list("action_items")
|
||||
if (actionItems.isNotEmpty()) {
|
||||
ShonarGhostButton({
|
||||
clipboard.setText(AnnotatedString(
|
||||
actionItems.joinToString("\n") { "- $it" }))
|
||||
copiedNote = "Action items copied"
|
||||
}) { Text("Copy action items") }
|
||||
}
|
||||
}
|
||||
copiedNote?.let {
|
||||
Text(it, style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.primary)
|
||||
}
|
||||
// Voice controls FIRST under the summary — before the long
|
||||
// sections. Every time they sat further down (page bottom,
|
||||
// then below the bullets) they fell off-screen and read as
|
||||
// "missing". They act on this summary; they live here.
|
||||
Text(s.short, style = MaterialTheme.typography.bodyLarge)
|
||||
// Voice controls directly under the paragraph —
|
||||
// they act on this summary and the shared card
|
||||
// says so. Every time they sat further down
|
||||
// (page bottom, below the bullets) they fell
|
||||
// off-screen and read as "missing".
|
||||
run {
|
||||
val report = detail.reportText
|
||||
val hasTranscript = detail.transcript != null ||
|
||||
(report != null && !report.contains("_No transcript available._"))
|
||||
(report != null &&
|
||||
!report.contains("_No transcript available._"))
|
||||
val remoteKnown = state.remoteIdFor(detail.file) != null
|
||||
val sumBusy = detail.jobs.firstOrNull { it.jobType == "summarize" }
|
||||
?.status in setOf("queued", "running")
|
||||
val voicesEnabled = remoteKnown && hasTranscript && !sumBusy
|
||||
Column(verticalArrangement = Arrangement.spacedBy(Shonar.Space1)) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(Shonar.Space2)) {
|
||||
Text("Re-summarize in the voice of",
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
|
|
@ -982,18 +973,66 @@ fun DetailScreen(state: DesktopState) {
|
|||
}
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.height(4.dp))
|
||||
// Full structured sections (key points, decisions, action
|
||||
// items, questions, details) right on screen — the saved
|
||||
// .md report had them but the live view showed only the
|
||||
// One-click copy: what gets pasted into
|
||||
// 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(Shonar.Space4))
|
||||
// Action items get their own card, first among the
|
||||
// sections: they are the content users actually carry out
|
||||
// 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
|
||||
// the file elsewhere.
|
||||
SUMMARY_LIST_KEYS.forEach { key ->
|
||||
// the file elsewhere. Section headings separate with
|
||||
// Space4 (more above the heading than below it).
|
||||
SUMMARY_LIST_KEYS.filter { it != "action_items" }.forEach { key ->
|
||||
val items = s.list(key)
|
||||
if (items.isNotEmpty()) {
|
||||
Spacer(Modifier.height(6.dp))
|
||||
Spacer(Modifier.height(Shonar.Space4))
|
||||
Text(key.replace('_', ' ').replaceFirstChar { it.uppercase() },
|
||||
style = MaterialTheme.typography.titleSmall)
|
||||
Spacer(Modifier.height(Shonar.Space1))
|
||||
items.forEach { item ->
|
||||
Text("• $item",
|
||||
style = MaterialTheme.typography.bodyMedium)
|
||||
|
|
@ -1001,11 +1040,12 @@ fun DetailScreen(state: DesktopState) {
|
|||
}
|
||||
}
|
||||
if (s.detailed.isNotBlank()) {
|
||||
Spacer(Modifier.height(6.dp))
|
||||
Spacer(Modifier.height(Shonar.Space4))
|
||||
Text("Details", style = MaterialTheme.typography.titleSmall)
|
||||
Spacer(Modifier.height(Shonar.Space1))
|
||||
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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue