implement markdown-based hint system and add MarkdownHint component

This commit is contained in:
jonasgaudian
2026-02-14 15:53:05 +01:00
parent f829174bcb
commit 306d0c7432
12 changed files with 415 additions and 3 deletions

View File

@@ -170,6 +170,9 @@ dependencies {
//noinspection UseTomlInstead //noinspection UseTomlInstead
implementation("com.pierfrancescosoffritti.androidyoutubeplayer:core:13.0.0") implementation("com.pierfrancescosoffritti.androidyoutubeplayer:core:13.0.0")
// Markdown rendering
implementation(libs.compose.markdown)
// Compression // Compression
testImplementation (libs.zstd.jni) testImplementation (libs.zstd.jni)
implementation(libs.zstd.jni.get().toString() + "@aar") implementation(libs.zstd.jni.get().toString() + "@aar")

View File

@@ -0,0 +1,86 @@
# How to Scan for AI Models
This guide explains how to use the **Scan** feature to discover and add AI models to your app.
## How Scanning Works
The scan feature searches for available AI models on your device or network.
> **Note:** Results depend on your API key permissions.
### Key Points
- Only public models are shown by default
- Private models require additional setup
- Try again if no models are found
## Why Some Models Are Missing
Some models may not appear in the scan results due to:
| Reason | Description | Icon |
|--------|-------------|------|
| Restricted access | Model requires special permissions | 🔒 |
| Not suitable | Model type not supported | ⚠️ |
| Text only | Only text-based models are supported | ✓ |
### Model Tiers
We recommend these tiers for optimal performance:
- **Nano** - Fastest, for simple tasks
- **Mini** - Balanced speed and capability
- **Small** - Good for most tasks
- **Medium** - More capable, slower
- **Large** - Most capable, paid only
## Tips for Success
1. **Verify your API key** is active and has correct permissions
2. **Select the correct organization** from your account
3. **Type model names manually** if scanning doesn't find them
4. **Prefer instruct or chat models** for text generation
```kotlin
// Example: Manual model addition
val model = Model(
name = "llama3.2",
type = ModelType.TEXT,
provider = "ollama"
)
```
## Visual Guide
### Step 1: Initiate Scan
Click the scan button to search for available models.
### Step 2: Select Model Type
Choose between different model categories:
- **Text Chat** - For conversational AI
- **Instruct** - For direct instructions
- **Complete** - For text completion
### Step 3: Add & Validate
Add the selected model and validate it works correctly.
---
## Can't Find Your Model?
If your model doesn't appear in the scan results:
1. Check if the model is running locally or accessible via API
2. Verify network connectivity
3. Try adding it manually by entering the model details
> **Pro Tip:** You can always add models manually by clicking the "+" button in the models screen.
---
*Last updated: 2024-01-15*
*For more help, visit our documentation website.*

View File

@@ -21,6 +21,11 @@ import androidx.compose.ui.unit.dp
import eu.gaudian.translator.R import eu.gaudian.translator.R
import eu.gaudian.translator.view.composable.AppIcons import eu.gaudian.translator.view.composable.AppIcons
/**
* TODO: Migrate to markdown-based hint format.
* Create a .md file in assets/hints/ and use MarkdownHint composable instead.
* See MarkdownHint.kt for implementation details.
*/
object ApiKeyHint: LegacyHint() { object ApiKeyHint: LegacyHint() {
override val titleRes: Int = R.string.hint_how_to_connect_to_an_ai override val titleRes: Int = R.string.hint_how_to_connect_to_an_ai

View File

@@ -21,6 +21,11 @@ import androidx.compose.ui.unit.dp
import eu.gaudian.translator.R import eu.gaudian.translator.R
import eu.gaudian.translator.view.composable.AppIcons import eu.gaudian.translator.view.composable.AppIcons
/**
* TODO: Migrate to markdown-based hint format.
* Create a .md file in assets/hints/ and use MarkdownHint composable instead.
* See MarkdownHint.kt for implementation details.
*/
object CategoryHint : LegacyHint() { object CategoryHint : LegacyHint() {
override val titleRes: Int = R.string.category_hint_intro override val titleRes: Int = R.string.category_hint_intro

View File

@@ -26,6 +26,11 @@ import androidx.compose.ui.unit.dp
import eu.gaudian.translator.R import eu.gaudian.translator.R
import eu.gaudian.translator.view.composable.AppIcons import eu.gaudian.translator.view.composable.AppIcons
/**
* TODO: Migrate to markdown-based hint format.
* Create a .md file in assets/hints/ and use MarkdownHint composable instead.
* See MarkdownHint.kt for implementation details.
*/
object CategoryHintScreen : LegacyHint() { object CategoryHintScreen : LegacyHint() {
override val titleRes: Int = R.string.category_hint_intro override val titleRes: Int = R.string.category_hint_intro

View File

@@ -0,0 +1,145 @@
package eu.gaudian.translator.view.hints
import android.content.Context
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import eu.gaudian.translator.R
/**
* Example of a migrated hint using the new markdown-based approach.
*
* This demonstrates how to migrate from the old LegacyHint format to the new
* markdown-based format.
*
* Benefits:
* - Easier to manage and translate (no code changes needed)
* - Better separation of concerns
* - Consistent styling across all hints
* - Support for rich formatting (tables, code blocks, etc.)
*/
/**
* Example 1: Loading markdown content from string (simple).
*/
@Composable
fun getApiKeyMarkdownHint(): String {
return """
# How to Connect to an AI Model
This guide explains how to connect your app to an AI model using an API key.
## Getting Started
To use AI models in your app, you need to provide a valid API key.
> **Note:** Keep your API key secure and never share it publicly.
## Key Status Indicators
| Status | Icon | Meaning |
|--------|------|---------|
| Active | ✅ | Key is valid and working |
| Missing | ⚠️ | Key is not set |
## Troubleshooting
1. Verify the key is correct
2. Ensure proper permissions
3. Check your quota
```json
{
"api_key": "sk-xxxxxxxxxxxx"
}
```
""".trimIndent()
}
@Composable
fun ApiKeyMarkdownHint() {
val content = getApiKeyMarkdownHint()
MarkdownHint(
markdownContent = content,
title = stringResource(R.string.hint_how_to_connect_to_an_ai)
)
}
/**
* Example 2: Loading markdown from assets file (recommended for production).
*/
@Composable
fun loadMarkdownFromAssets(fileName: String): String {
val context = LocalContext.current
return try {
context.assets.open("hints/$fileName").bufferedReader().use { it.readText() }
} catch (e: Exception) {
"Error loading markdown file: ${e.message}"
}
}
/**
* Data class for programmatic hint loading.
*/
data class MarkdownHintDefinition(
val fileName: String,
val titleRes: Int
) {
fun loadContent(context: Context): String {
return context.assets.open("hints/$fileName").bufferedReader().use { it.readText() }
}
}
/**
* Pre-defined hints ready for migration.
*/
object MarkdownHints {
val API_KEY = MarkdownHintDefinition(
fileName = "api_key_hint.md",
titleRes = R.string.hint_how_to_connect_to_an_ai
)
val CATEGORY = MarkdownHintDefinition(
fileName = "category_hint.md",
titleRes = R.string.category_hint_intro
)
val LEARNING_STAGES = MarkdownHintDefinition(
fileName = "learning_stages_hint.md",
titleRes = R.string.learning_stages_title
)
val SORTING = MarkdownHintDefinition(
fileName = "sorting_hint.md",
titleRes = R.string.sorting_hint_title
)
val VOCABULARY_PROGRESS = MarkdownHintDefinition(
fileName = "vocabulary_progress_hint.md",
titleRes = R.string.hint_vocabulary_progress_hint_title
)
}
/**
* Preview for the migrated API Key hint.
*/
@Preview
@Composable
fun ApiKeyMarkdownHintPreview() {
MaterialTheme {
ApiKeyMarkdownHint()
}
}
/**
* Preview for loading from assets.
*/
@Preview
@Composable
fun LoadFromAssetsPreview() {
MaterialTheme {
val content = loadMarkdownFromAssets("example_hint.md")
MarkdownHint(
markdownContent = content,
title = stringResource(R.string.hint_title_hints_overview)
)
}
}

View File

@@ -32,6 +32,11 @@ private data class LearningStage(
val interval: String? = null val interval: String? = null
) )
/**
* TODO: Migrate to markdown-based hint format.
* Create a .md file in assets/hints/ and use MarkdownHint composable instead.
* See MarkdownHint.kt for implementation details.
*/
object LearningStagesHint : LegacyHint() { object LearningStagesHint : LegacyHint() {
override val titleRes: Int = R.string.learning_stages_title override val titleRes: Int = R.string.learning_stages_title

View File

@@ -0,0 +1,147 @@
package eu.gaudian.translator.view.hints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import dev.jeziellago.compose.markdowntext.MarkdownText
import eu.gaudian.translator.R
/**
* Markdown-styled hint content using the jeziellago compose-markdown library.
* This provides beautiful, consistent rendering of markdown content with
* support for headings, lists, tables, code blocks, and more.
*
* Usage:
* - Create a .md file in assets/hints/ (e.g., "api_key_hint.md")
* - Load it using context.assets.open() or pass raw markdown string
* - Use MarkdownHint composable for styled rendering
*
* Supported markdown:
* - Headings (# ## ###)
* - Bold (**text**) and italic (*text*)
* - Lists (- item, 1. item)
* - Tables (| col | col |)
* - Code blocks (```code```)
* - Blockquotes (> quote)
* - Links ([text](url))
*/
@Composable
fun MarkdownHint(
markdownContent: String,
modifier: Modifier = Modifier,
title: String? = null
) {
val scrollState = rememberScrollState()
Column(
modifier = modifier
.fillMaxWidth()
.verticalScroll(scrollState)
.padding(16.dp)
) {
// Optional title with icon header
title?.let {
HeaderWithIcon(
title = it,
subtitle = null
)
Spacer(modifier = Modifier.height(16.dp))
}
// Render markdown
MarkdownText(
markdown = markdownContent,
modifier = Modifier.fillMaxWidth(),
style = MaterialTheme.typography.bodyMedium.copy(
color = MaterialTheme.colorScheme.onSurface
)
)
}
}
/**
* Preview for MarkdownHint with sample content.
*/
@Preview
@Composable
fun MarkdownHintPreview() {
val sampleMarkdown = """
# Welcome to the Hint System
This is a **markdown-based** hint that provides _beautiful_ and consistent styling.
## Features
- **Rich text formatting** - bold, italic, strikethrough
- **Headings** - H1 through H6 levels
- **Lists** - ordered and unordered
- **Code blocks** - with syntax highlighting
- **Tables** - for structured data
- **Links** - styled clickable references
- **Blockquotes** - for highlighted content
## How to Use
1. Create a `.md` file in `assets/hints/`
2. Load it using `context.assets.open("hints/your_hint.md")`
3. Pass the content to `MarkdownHint()`
> **Tip:** You can also embed UI elements by combining markdown with HintElements!
## Code Example
```kotlin
val markdown = loadMarkdownFromAssets("hints/scan_hint.md")
MarkdownHint(
markdownContent = markdown,
title = "Scan Hint"
)
```
## Table Example
| Feature | Status | Priority |
|---------|--------|----------|
| Headings | ✅ | High |
| Lists | ✅ | High |
| Tables | ✅ | Medium |
---
*Last updated: 2024-01-15*
""".trimIndent()
MaterialTheme {
MarkdownHint(
markdownContent = sampleMarkdown,
title = stringResource(R.string.hint_title_hints_overview)
)
}
}
/**
* Data class for loading markdown content from assets.
*/
data class MarkdownHintData(
val fileName: String,
val titleRes: Int
) {
/**
* Load and return the markdown content as a string.
*/
fun loadContent(androidContext: android.content.Context): String {
return androidContext.assets.open("hints/$fileName")
.bufferedReader()
.use { it.readText() }
}
}

View File

@@ -38,6 +38,11 @@ import eu.gaudian.translator.view.composable.AppButton
import eu.gaudian.translator.view.composable.AppIcons import eu.gaudian.translator.view.composable.AppIcons
import eu.gaudian.translator.view.composable.AppOutlinedTextField import eu.gaudian.translator.view.composable.AppOutlinedTextField
/**
* TODO: Migrate to markdown-based hint format.
* Create a .md file in assets/hints/ and use MarkdownHint composable instead.
* See MarkdownHint.kt for implementation details.
*/
object SortingScreenHint : LegacyHint() { object SortingScreenHint : LegacyHint() {
override val titleRes: Int = R.string.sorting_hint_title override val titleRes: Int = R.string.sorting_hint_title

View File

@@ -21,6 +21,11 @@ import androidx.compose.ui.unit.dp
import eu.gaudian.translator.R import eu.gaudian.translator.R
import eu.gaudian.translator.view.composable.AppIcons import eu.gaudian.translator.view.composable.AppIcons
/**
* TODO: Migrate to markdown-based hint format.
* Create a .md file in assets/hints/ and use MarkdownHint composable instead.
* See MarkdownHint.kt for implementation details.
*/
object VocabularyProgressHint : LegacyHint() { object VocabularyProgressHint : LegacyHint() {
override val titleRes: Int = R.string.hint_vocabulary_progress_hint_title override val titleRes: Int = R.string.hint_vocabulary_progress_hint_title

View File

@@ -41,6 +41,8 @@ room = "2.8.4"
coreKtxVersion = "1.7.0" coreKtxVersion = "1.7.0"
truth = "1.4.5" truth = "1.4.5"
zstdJni = "1.5.7-7" zstdJni = "1.5.7-7"
composeMarkdown = "0.5.8"
jitpack = "1.0.10"
[libraries] [libraries]
@@ -100,6 +102,7 @@ hilt-android = { module = "com.google.dagger:hilt-android", version = "2.59.1" }
hilt-android-compiler = { module = "com.google.dagger:hilt-android-compiler", version = "2.59.1" } hilt-android-compiler = { module = "com.google.dagger:hilt-android-compiler", version = "2.59.1" }
hilt-navigation-compose = { module = "androidx.hilt:hilt-navigation-compose", version = "1.3.0" } hilt-navigation-compose = { module = "androidx.hilt:hilt-navigation-compose", version = "1.3.0" }
mockk = { module = "io.mockk:mockk", version = "1.14.9" } mockk = { module = "io.mockk:mockk", version = "1.14.9" }
compose-markdown = { module = "com.github.jeziellago:compose-markdown", version.ref = "composeMarkdown" }
[plugins] [plugins]
android-application = { id = "com.android.application", version.ref = "agp" } android-application = { id = "com.android.application", version.ref = "agp" }

View File

@@ -14,15 +14,13 @@ pluginManagement {
} }
} }
dependencyResolutionManagement { dependencyResolutionManagement {
@Suppress("UnstableApiUsage")
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
@Suppress("UnstableApiUsage")
repositories { repositories {
google() google()
mavenCentral() mavenCentral()
maven { url = uri("https://jitpack.io") }
} }
} }
rootProject.name = "Translator" rootProject.name = "Translator"
include(":app") include(":app")