Skip to content

Commit

Permalink
Markdown support (#1960)
Browse files Browse the repository at this point in the history

---------

Co-authored-by: yschimke <[email protected]>
  • Loading branch information
yschimke and yschimke authored Jan 12, 2024
1 parent 8c9c9e1 commit fa6d09c
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,38 +28,45 @@ import com.google.android.horologist.ai.core.serviceInfo
import com.google.android.horologist.ai.core.textResponse
import com.google.protobuf.Empty

class DummyInferenceServiceImpl(val thisId: String) : InferenceServiceGrpcKt.InferenceServiceCoroutineImplBase() {
override suspend fun answerPrompt(request: PromptRequest): Response {
if (request.modelId.id != thisId) {
return response {
failure = failure {
message = "Unknown model ${request.modelId.id}"
class DummyInferenceServiceImpl(val thisId: String) :
InferenceServiceGrpcKt.InferenceServiceCoroutineImplBase() {
override suspend fun answerPrompt(request: PromptRequest): Response {
if (request.modelId.id != thisId) {
return response {
failure = failure {
message = "Unknown model ${request.modelId.id}"
}
}
}
} else if (request.prompt.hasTextPrompt()) {
val query = request.prompt.textPrompt.text
return response {
textResponse = textResponse {
text =
"I didn't understand '$query'.\nPlease try again with a different question. $thisId"
} else if (request.prompt.hasTextPrompt()) {
val query = request.prompt.textPrompt.text
return response {
textResponse = textResponse {
text = """
I didn't understand.
> $query.
Please try again with a different question.
From *$thisId*
""".trimIndent()
}
}
}
} else {
return response {
failure = failure {
message = "Unhandled request type $request"
} else {
return response {
failure = failure {
message = "Unhandled request type $request"
}
}
}
}
}

override suspend fun serviceInfo(request: Empty): ServiceInfo {
return serviceInfo {
name = "Dummy $thisId"
models += modelInfo {
modelId = modelId { id = thisId }
override suspend fun serviceInfo(request: Empty): ServiceInfo {
return serviceInfo {
name = "Dummy $thisId"
models += modelInfo {
modelId = modelId { id = thisId }
name = "Dummy $thisId"
}
}
}
}
}
2 changes: 2 additions & 0 deletions ai/sample/wear-prompt-app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ dependencies {
implementation(libs.wearcompose.foundation)
implementation(libs.wearcompose.navigation)

implementation(libs.mikepenz.markdown)

debugImplementation(libs.compose.ui.tooling)
implementation(libs.androidx.wear.tooling.preview)
debugImplementation(projects.composeTools)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,26 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Mic
import androidx.compose.material.icons.filled.QuestionAnswer
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.wear.compose.material.Card
import androidx.wear.compose.material.CardDefaults
import androidx.wear.compose.material.LocalContentColor
import androidx.wear.compose.material.MaterialTheme
import androidx.wear.compose.ui.tooling.preview.WearPreviewLargeRound
import androidx.wear.compose.ui.tooling.preview.WearPreviewSmallRound
import com.google.android.horologist.ai.sample.prompt.R
import com.google.android.horologist.ai.ui.components.PromptOrResponseDisplay
import com.google.android.horologist.ai.ui.model.ModelInstanceUiModel
import com.google.android.horologist.ai.ui.model.PromptOrResponseUiModel
import com.google.android.horologist.ai.ui.model.TextPromptUiModel
import com.google.android.horologist.ai.ui.model.TextResponseUiModel
import com.google.android.horologist.ai.ui.screens.PromptScreen
Expand All @@ -44,6 +55,11 @@ import com.google.android.horologist.compose.layout.ScalingLazyColumnState
import com.google.android.horologist.compose.layout.ScreenScaffold
import com.google.android.horologist.compose.layout.rememberColumnState
import com.google.android.horologist.compose.material.Button
import com.mikepenz.markdown.compose.LocalMarkdownColors
import com.mikepenz.markdown.compose.LocalMarkdownTypography
import com.mikepenz.markdown.compose.Markdown
import com.mikepenz.markdown.model.markdownColor
import com.mikepenz.markdown.model.markdownTypography

@Composable
fun SamplePromptScreen(
Expand Down Expand Up @@ -106,12 +122,80 @@ private fun SamplePromptScreen(
promptEntry: @Composable () -> Unit,
) {
ScreenScaffold(scrollState = columnState) {
PromptScreen(
uiState = uiState,
columnState = columnState,
modifier = modifier,
promptEntry = promptEntry,
onSettingsClick = onSettingsClick,
CompositionLocalProvider(
LocalMarkdownColors provides SampleColors(),
LocalMarkdownTypography provides SampleTypography(),
) {
PromptScreen(
uiState = uiState,
columnState = columnState,
modifier = modifier,
promptEntry = promptEntry,
onSettingsClick = onSettingsClick,
promptDisplay = {
ModelDisplay(it)
},
)
}
}
}

@Composable
private fun SampleTypography() = markdownTypography(
h1 = MaterialTheme.typography.title1,
h2 = MaterialTheme.typography.title2,
h3 = MaterialTheme.typography.title3,
h4 = MaterialTheme.typography.caption1,
h5 = MaterialTheme.typography.caption2,
h6 = MaterialTheme.typography.caption3,
text = MaterialTheme.typography.body1,
code = MaterialTheme.typography.body2.copy(fontFamily = FontFamily.Monospace),
quote = MaterialTheme.typography.body2.plus(SpanStyle(fontStyle = FontStyle.Italic)),
paragraph = MaterialTheme.typography.body1,
ordered = MaterialTheme.typography.body1,
bullet = MaterialTheme.typography.body1,
list = MaterialTheme.typography.body1,
)

@Composable
private fun SampleColors() = markdownColor(
text = Color.White,
codeText = LocalContentColor.current,
linkText = Color.Blue,
codeBackground = MaterialTheme.colors.background,
inlineCodeBackground = MaterialTheme.colors.background,
)

@Composable
private fun ModelDisplay(it: PromptOrResponseUiModel) {
if (it is TextResponseUiModel) {
SampleTextResponseCard(it)
} else {
PromptOrResponseDisplay(
promptResponse = it,
onClick = {},
)
}
}

@Composable
public fun SampleTextResponseCard(
textResponseUiModel: TextResponseUiModel,
modifier: Modifier = Modifier,
onClick: () -> Unit = {},
) {
Card(
modifier = modifier.fillMaxWidth(),
onClick = onClick,
backgroundPainter = CardDefaults.cardBackgroundPainter(
MaterialTheme.colors.surface,
MaterialTheme.colors.surface,
),
) {
Markdown(
textResponseUiModel.text,
colors = SampleColors(),
typography = SampleTypography(),
)
}
}
Expand Down Expand Up @@ -186,3 +270,24 @@ fun SamplePromptScreenPreviewQuestion() {
},
)
}

@WearPreviewLargeRound
@Composable
fun SamplePromptScreenPreviewMarkdown() {
SamplePromptScreen(
uiState = PromptUiState(
ModelInstanceUiModel("id", "Demo Model"),
listOf(
TextPromptUiModel("why did the chicken cross the road?"),
TextResponseUiModel("To **get** to _the_ other side."),
),
),
promptEntry = {
Button(
imageVector = Icons.Default.QuestionAnswer,
contentDescription = "Ask Again",
onClick = { },
)
},
)
}
2 changes: 1 addition & 1 deletion ai/ui/api/current.api
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ package com.google.android.horologist.ai.ui.model {
package com.google.android.horologist.ai.ui.screens {

public final class PromptScreenKt {
method @androidx.compose.runtime.Composable public static void PromptScreen(com.google.android.horologist.ai.ui.screens.PromptUiState uiState, optional androidx.compose.ui.Modifier modifier, optional com.google.android.horologist.compose.layout.ScalingLazyColumnState columnState, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onSettingsClick, kotlin.jvm.functions.Function0<kotlin.Unit> promptEntry);
method @androidx.compose.runtime.Composable public static void PromptScreen(com.google.android.horologist.ai.ui.screens.PromptUiState uiState, optional androidx.compose.ui.Modifier modifier, optional com.google.android.horologist.compose.layout.ScalingLazyColumnState columnState, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onSettingsClick, optional kotlin.jvm.functions.Function1<? super com.google.android.horologist.ai.ui.model.PromptOrResponseUiModel,kotlin.Unit> promptDisplay, kotlin.jvm.functions.Function0<kotlin.Unit> promptEntry);
}

public final class PromptUiState {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.google.android.horologist.ai.ui.screens

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
Expand All @@ -30,6 +31,7 @@ import com.google.android.horologist.ai.ui.components.PromptOrResponseDisplay
import com.google.android.horologist.ai.ui.components.ResponseInProgressCard
import com.google.android.horologist.ai.ui.components.TextPromptDisplay
import com.google.android.horologist.ai.ui.model.InProgressResponseUiModel
import com.google.android.horologist.ai.ui.model.PromptOrResponseUiModel
import com.google.android.horologist.ai.ui.model.PromptUiModel
import com.google.android.horologist.ai.ui.model.ResponseUiModel
import com.google.android.horologist.compose.layout.ScalingLazyColumn
Expand All @@ -53,6 +55,12 @@ public fun PromptScreen(
),
),
onSettingsClick: (() -> Unit)? = null,
promptDisplay: @Composable (PromptOrResponseUiModel) -> Unit = {
PromptOrResponseDisplay(
promptResponse = it,
onClick = {},
)
},
promptEntry: @Composable () -> Unit,
) {
ScalingLazyColumn(columnState = columnState, modifier = modifier) {
Expand All @@ -68,13 +76,13 @@ public fun PromptScreen(
is ResponseUiModel -> PaddingValues(start = 20.dp)
else -> PaddingValues()
}
PromptOrResponseDisplay(
promptResponse = it,
onClick = {},
Box(
modifier = Modifier
.fillMaxWidth()
.padding(padding),
)
) {
promptDisplay(it)
}
}
}
val inProgress = uiState.inProgress
Expand Down
1 change: 1 addition & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ kotlinx-coroutines-playservices = { module = "org.jetbrains.kotlinx:kotlinx-coro
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinxCoroutine" }
lottie-compose = "com.airbnb.android:lottie-compose:6.3.0"
metalavaGradle = { module = "me.tylerbwong.gradle.metalava:plugin", version.ref = "metalava" }
mikepenz-markdown = "com.mikepenz:multiplatform-markdown-renderer:0.10.0"
moshi = { module = "com.squareup.moshi:moshi", version.ref = "moshi" }
moshi-adapters = { module = "com.squareup.moshi:moshi-adapters", version.ref = "moshi" }
moshi-kotlin = { module = "com.squareup.moshi:moshi-kotlin", version.ref = "moshi" }
Expand Down

0 comments on commit fa6d09c

Please sign in to comment.