From 306d0c74322c7d6d25744fe9215135ad1b2ecda8 Mon Sep 17 00:00:00 2001 From: jonasgaudian <43753916+jonasgaudian@users.noreply.github.com> Date: Sat, 14 Feb 2026 15:53:05 +0100 Subject: [PATCH] implement markdown-based hint system and add `MarkdownHint` component --- app/build.gradle.kts | 3 + app/src/main/assets/hints/example_hint.md | 86 ++++++++++ .../translator/view/hints/ApiKeyHints.kt | 5 + .../translator/view/hints/CategoryHint.kt | 5 + .../view/hints/CategoryHintScreen.kt | 5 + .../view/hints/ExampleMigratedHint.kt | 145 +++++++++++++++++ .../view/hints/LearningStagesHint.kt | 5 + .../translator/view/hints/MarkdownHint.kt | 147 ++++++++++++++++++ .../view/hints/SortingScreenHint.kt | 5 + .../view/hints/VocabularyProgressHint.kt | 5 + gradle/libs.versions.toml | 3 + settings.gradle.kts | 4 +- 12 files changed, 415 insertions(+), 3 deletions(-) create mode 100644 app/src/main/assets/hints/example_hint.md create mode 100644 app/src/main/java/eu/gaudian/translator/view/hints/ExampleMigratedHint.kt create mode 100644 app/src/main/java/eu/gaudian/translator/view/hints/MarkdownHint.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 13c1c65..c8a85cd 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -170,6 +170,9 @@ dependencies { //noinspection UseTomlInstead implementation("com.pierfrancescosoffritti.androidyoutubeplayer:core:13.0.0") + // Markdown rendering + implementation(libs.compose.markdown) + // Compression testImplementation (libs.zstd.jni) implementation(libs.zstd.jni.get().toString() + "@aar") diff --git a/app/src/main/assets/hints/example_hint.md b/app/src/main/assets/hints/example_hint.md new file mode 100644 index 0000000..c667281 --- /dev/null +++ b/app/src/main/assets/hints/example_hint.md @@ -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.* diff --git a/app/src/main/java/eu/gaudian/translator/view/hints/ApiKeyHints.kt b/app/src/main/java/eu/gaudian/translator/view/hints/ApiKeyHints.kt index 7a5506a..a25ab61 100644 --- a/app/src/main/java/eu/gaudian/translator/view/hints/ApiKeyHints.kt +++ b/app/src/main/java/eu/gaudian/translator/view/hints/ApiKeyHints.kt @@ -21,6 +21,11 @@ import androidx.compose.ui.unit.dp import eu.gaudian.translator.R 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() { override val titleRes: Int = R.string.hint_how_to_connect_to_an_ai diff --git a/app/src/main/java/eu/gaudian/translator/view/hints/CategoryHint.kt b/app/src/main/java/eu/gaudian/translator/view/hints/CategoryHint.kt index 2d9cda9..1a71046 100644 --- a/app/src/main/java/eu/gaudian/translator/view/hints/CategoryHint.kt +++ b/app/src/main/java/eu/gaudian/translator/view/hints/CategoryHint.kt @@ -21,6 +21,11 @@ import androidx.compose.ui.unit.dp import eu.gaudian.translator.R 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() { override val titleRes: Int = R.string.category_hint_intro diff --git a/app/src/main/java/eu/gaudian/translator/view/hints/CategoryHintScreen.kt b/app/src/main/java/eu/gaudian/translator/view/hints/CategoryHintScreen.kt index 53521ca..ca0dda1 100644 --- a/app/src/main/java/eu/gaudian/translator/view/hints/CategoryHintScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/hints/CategoryHintScreen.kt @@ -26,6 +26,11 @@ import androidx.compose.ui.unit.dp import eu.gaudian.translator.R 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() { override val titleRes: Int = R.string.category_hint_intro diff --git a/app/src/main/java/eu/gaudian/translator/view/hints/ExampleMigratedHint.kt b/app/src/main/java/eu/gaudian/translator/view/hints/ExampleMigratedHint.kt new file mode 100644 index 0000000..46ff79c --- /dev/null +++ b/app/src/main/java/eu/gaudian/translator/view/hints/ExampleMigratedHint.kt @@ -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) + ) + } +} diff --git a/app/src/main/java/eu/gaudian/translator/view/hints/LearningStagesHint.kt b/app/src/main/java/eu/gaudian/translator/view/hints/LearningStagesHint.kt index 886e1db..efcc3c7 100644 --- a/app/src/main/java/eu/gaudian/translator/view/hints/LearningStagesHint.kt +++ b/app/src/main/java/eu/gaudian/translator/view/hints/LearningStagesHint.kt @@ -32,6 +32,11 @@ private data class LearningStage( 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() { override val titleRes: Int = R.string.learning_stages_title diff --git a/app/src/main/java/eu/gaudian/translator/view/hints/MarkdownHint.kt b/app/src/main/java/eu/gaudian/translator/view/hints/MarkdownHint.kt new file mode 100644 index 0000000..5117ad7 --- /dev/null +++ b/app/src/main/java/eu/gaudian/translator/view/hints/MarkdownHint.kt @@ -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() } + } +} diff --git a/app/src/main/java/eu/gaudian/translator/view/hints/SortingScreenHint.kt b/app/src/main/java/eu/gaudian/translator/view/hints/SortingScreenHint.kt index c300826..e083f85 100644 --- a/app/src/main/java/eu/gaudian/translator/view/hints/SortingScreenHint.kt +++ b/app/src/main/java/eu/gaudian/translator/view/hints/SortingScreenHint.kt @@ -38,6 +38,11 @@ import eu.gaudian.translator.view.composable.AppButton import eu.gaudian.translator.view.composable.AppIcons 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() { override val titleRes: Int = R.string.sorting_hint_title diff --git a/app/src/main/java/eu/gaudian/translator/view/hints/VocabularyProgressHint.kt b/app/src/main/java/eu/gaudian/translator/view/hints/VocabularyProgressHint.kt index 2f3ef2d..9ac2aeb 100644 --- a/app/src/main/java/eu/gaudian/translator/view/hints/VocabularyProgressHint.kt +++ b/app/src/main/java/eu/gaudian/translator/view/hints/VocabularyProgressHint.kt @@ -21,6 +21,11 @@ import androidx.compose.ui.unit.dp import eu.gaudian.translator.R 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() { override val titleRes: Int = R.string.hint_vocabulary_progress_hint_title diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 13f0835..b06cd35 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -41,6 +41,8 @@ room = "2.8.4" coreKtxVersion = "1.7.0" truth = "1.4.5" zstdJni = "1.5.7-7" +composeMarkdown = "0.5.8" +jitpack = "1.0.10" [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-navigation-compose = { module = "androidx.hilt:hilt-navigation-compose", version = "1.3.0" } mockk = { module = "io.mockk:mockk", version = "1.14.9" } +compose-markdown = { module = "com.github.jeziellago:compose-markdown", version.ref = "composeMarkdown" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" } diff --git a/settings.gradle.kts b/settings.gradle.kts index 35312db..0e79b93 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -14,15 +14,13 @@ pluginManagement { } } dependencyResolutionManagement { - @Suppress("UnstableApiUsage") repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) - @Suppress("UnstableApiUsage") repositories { google() mavenCentral() + maven { url = uri("https://jitpack.io") } } } rootProject.name = "Translator" include(":app") - \ No newline at end of file