Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion app/android/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ val useGpuForLlm = project.findProperty("useGpuForLlm")?.toString()?.toBoolean()
// long retrieved-context prompts + constrained medical prose. Re-test before re-enabling.
val useMtpForLlm = project.findProperty("useMtpForLlm")?.toString()?.toBoolean() ?: false

// Used in both the dependency declaration below and the BuildConfig field so the About page
// can surface what's actually linked at build time. Update in lockstep with the dependency.
val litertlmVersion = "0.11.0"

fun propOrEnv(envName: String, propertyName: String): String? =
System.getenv(envName)?.takeIf { it.isNotBlank() }
?: (keystoreProperties.getProperty(propertyName)?.takeIf { it.isNotBlank() })
Expand Down Expand Up @@ -57,6 +61,7 @@ android {
versionName = flutter.versionName
buildConfigField("boolean", "USE_GPU_FOR_LLM", useGpuForLlm.toString())
buildConfigField("boolean", "USE_MTP_FOR_LLM", useMtpForLlm.toString())
buildConfigField("String", "LITERTLM_VERSION", "\"$litertlmVersion\"")
}

sourceSets {
Expand Down Expand Up @@ -104,7 +109,7 @@ kotlin {
}
dependencies {
implementation("com.google.ai.edge.localagents:localagents-rag:0.2.0")
implementation("com.google.ai.edge.litertlm:litertlm-android:0.11.0")
implementation("com.google.ai.edge.litertlm:litertlm-android:$litertlmVersion")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-guava:1.10.2")
implementation("com.google.protobuf:protobuf-javalite:3.25.4")
}
Expand Down
11 changes: 11 additions & 0 deletions app/android/app/src/main/kotlin/com/example/app/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ class MainActivity : FlutterActivity() {
"getPinnedRagBundleInfo" -> {
result.success(getPinnedRagBundleInfo())
}
"getRuntimeInfo" -> {
result.success(getRuntimeInfo())
}
"startDownloadService" -> {
// Start the ForegroundService so the OS keeps the process alive
// while downloads run in the background. We do NOT request
Expand Down Expand Up @@ -184,6 +187,14 @@ class MainActivity : FlutterActivity() {
null
}

private fun getRuntimeInfo(): Map<String, Any> = mapOf(
"appVersionName" to BuildConfig.VERSION_NAME,
"appVersionCode" to BuildConfig.VERSION_CODE,
"litertlmVersion" to BuildConfig.LITERTLM_VERSION,
"llmBackendConfigured" to if (BuildConfig.USE_GPU_FOR_LLM) "GPU" else "CPU",
"mtpEnabled" to BuildConfig.USE_MTP_FOR_LLM,
)

private fun openPdf(source: String, page: Int): Boolean {
val baseFolder = application.getExternalFilesDir(null) ?: return false
val normalizedSource = normalizeSourceId(source)
Expand Down
5 changes: 5 additions & 0 deletions app/lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@
"aboutKnowledgeBundleUnavailable": "Bundle metadata is not available on this device yet.",
"aboutKnowledgeBundleVersionLabel": "Version",
"aboutKnowledgeBundleDeployedLabel": "Deployed",
"aboutKnowledgeBundlePinnedNote": "Pinned in this build; deployment receipt not found on device.",
"aboutRuntimeTitle": "Runtime",
"aboutRuntimeAppLabel": "App",
"aboutRuntimeLitertlmLabel": "LiteRT-LM",
"aboutRuntimeBackendLabel": "LLM backend",

"drawerAbout": "About MAM-AI",
"drawerTitle": "Past conversations",
Expand Down
30 changes: 30 additions & 0 deletions app/lib/l10n/app_localizations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,36 @@ abstract class AppLocalizations {
/// **'Deployed'**
String get aboutKnowledgeBundleDeployedLabel;

/// No description provided for @aboutKnowledgeBundlePinnedNote.
///
/// In en, this message translates to:
/// **'Pinned in this build; deployment receipt not found on device.'**
String get aboutKnowledgeBundlePinnedNote;

/// No description provided for @aboutRuntimeTitle.
///
/// In en, this message translates to:
/// **'Runtime'**
String get aboutRuntimeTitle;

/// No description provided for @aboutRuntimeAppLabel.
///
/// In en, this message translates to:
/// **'App'**
String get aboutRuntimeAppLabel;

/// No description provided for @aboutRuntimeLitertlmLabel.
///
/// In en, this message translates to:
/// **'LiteRT-LM'**
String get aboutRuntimeLitertlmLabel;

/// No description provided for @aboutRuntimeBackendLabel.
///
/// In en, this message translates to:
/// **'LLM backend'**
String get aboutRuntimeBackendLabel;

/// No description provided for @drawerAbout.
///
/// In en, this message translates to:
Expand Down
16 changes: 16 additions & 0 deletions app/lib/l10n/app_localizations_en.dart
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,22 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get aboutKnowledgeBundleDeployedLabel => 'Deployed';

@override
String get aboutKnowledgeBundlePinnedNote =>
'Pinned in this build; deployment receipt not found on device.';

@override
String get aboutRuntimeTitle => 'Runtime';

@override
String get aboutRuntimeAppLabel => 'App';

@override
String get aboutRuntimeLitertlmLabel => 'LiteRT-LM';

@override
String get aboutRuntimeBackendLabel => 'LLM backend';

@override
String get drawerAbout => 'About MAM-AI';

Expand Down
16 changes: 16 additions & 0 deletions app/lib/l10n/app_localizations_sw.dart
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,22 @@ class AppLocalizationsSw extends AppLocalizations {
@override
String get aboutKnowledgeBundleDeployedLabel => 'Kimepelekwa';

@override
String get aboutKnowledgeBundlePinnedNote =>
'Pinned in this build; deployment receipt not found on device.';

@override
String get aboutRuntimeTitle => 'Runtime';

@override
String get aboutRuntimeAppLabel => 'App';

@override
String get aboutRuntimeLitertlmLabel => 'LiteRT-LM';

@override
String get aboutRuntimeBackendLabel => 'LLM backend';

@override
String get drawerAbout => 'Kuhusu MAM-AI';

Expand Down
5 changes: 5 additions & 0 deletions app/lib/l10n/app_sw.arb
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@
"aboutKnowledgeBundleUnavailable": "Taarifa za kifurushi bado hazipatikani kwenye kifaa hiki.",
"aboutKnowledgeBundleVersionLabel": "Toleo",
"aboutKnowledgeBundleDeployedLabel": "Kimepelekwa",
"aboutKnowledgeBundlePinnedNote": "Pinned in this build; deployment receipt not found on device.",
"aboutRuntimeTitle": "Runtime",
"aboutRuntimeAppLabel": "App",
"aboutRuntimeLitertlmLabel": "LiteRT-LM",
"aboutRuntimeBackendLabel": "LLM backend",

"drawerAbout": "Kuhusu MAM-AI",
"drawerTitle": "Mazungumzo ya zamani",
Expand Down
138 changes: 132 additions & 6 deletions app/lib/screens/about_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,36 @@ class _AboutPageState extends State<AboutPage> {
);

late final Future<RagBundleInfo?> _bundleInfoFuture;
late final Future<RuntimeInfo?> _runtimeInfoFuture;

@override
void initState() {
super.initState();
_bundleInfoFuture = _loadBundleInfo();
_runtimeInfoFuture = _loadRuntimeInfo();
}

Future<RagBundleInfo?> _loadBundleInfo() async {
final deployed = await _invokeMap("getDeployedRagBundleInfo");
if (deployed != null) {
return RagBundleInfo.fromMap(deployed, source: BundleInfoSource.deployed);
}
final pinned = await _invokeMap("getPinnedRagBundleInfo");
if (pinned != null) {
return RagBundleInfo.fromMap(pinned, source: BundleInfoSource.pinned);
}
return null;
}

Future<RuntimeInfo?> _loadRuntimeInfo() async {
final raw = await _invokeMap("getRuntimeInfo");
if (raw == null) return null;
return RuntimeInfo.fromMap(raw);
}

Future<Map<String, dynamic>?> _invokeMap(String method) async {
try {
final raw = await _platform.invokeMapMethod<String, dynamic>(
"getDeployedRagBundleInfo",
);
if (raw == null) return null;
return RagBundleInfo.fromMap(raw);
return await _platform.invokeMapMethod<String, dynamic>(method);
} on PlatformException {
return null;
} on MissingPluginException {
Expand Down Expand Up @@ -78,6 +94,8 @@ class _AboutPageState extends State<AboutPage> {
),
const SizedBox(height: 36),
_BundleInfoCard(bundleInfoFuture: _bundleInfoFuture),
const SizedBox(height: 16),
_RuntimeInfoCard(runtimeInfoFuture: _runtimeInfoFuture),
const SizedBox(height: 36),

// Partnership
Expand Down Expand Up @@ -110,19 +128,51 @@ class _AboutPageState extends State<AboutPage> {
}
}

enum BundleInfoSource { deployed, pinned }

class RagBundleInfo {
final String bundleVersion;
final String deployedAtUtc;
final BundleInfoSource source;

const RagBundleInfo({
required this.bundleVersion,
required this.deployedAtUtc,
required this.source,
});

factory RagBundleInfo.fromMap(Map<String, dynamic> raw) {
factory RagBundleInfo.fromMap(
Map<String, dynamic> raw, {
required BundleInfoSource source,
}) {
return RagBundleInfo(
bundleVersion: raw['bundleVersion']?.toString() ?? '',
deployedAtUtc: raw['deployedAtUtc']?.toString() ?? '',
source: source,
);
}
}

class RuntimeInfo {
final String appVersion;
final String litertlmVersion;
final String llmBackend;

const RuntimeInfo({
required this.appVersion,
required this.litertlmVersion,
required this.llmBackend,
});

factory RuntimeInfo.fromMap(Map<String, dynamic> raw) {
final name = raw['appVersionName']?.toString() ?? '';
final code = raw['appVersionCode']?.toString() ?? '';
final appVersion =
code.isNotEmpty && name.isNotEmpty ? '$name ($code)' : name;
return RuntimeInfo(
appVersion: appVersion,
litertlmVersion: raw['litertlmVersion']?.toString() ?? '',
llmBackend: raw['llmBackendConfigured']?.toString() ?? '',
);
}
}
Expand Down Expand Up @@ -206,6 +256,82 @@ class _BundleInfoCard extends StatelessWidget {
value: info.deployedAtUtc,
),
],
if (info.source == BundleInfoSource.pinned) ...[
const SizedBox(height: 10),
Text(
l10n.aboutKnowledgeBundlePinnedNote,
style: const TextStyle(
fontSize: 12,
fontStyle: FontStyle.italic,
color: Color(0xff786A5E),
height: 1.4,
),
),
],
],
);
},
),
);
}
}

class _RuntimeInfoCard extends StatelessWidget {
final Future<RuntimeInfo?> runtimeInfoFuture;

const _RuntimeInfoCard({required this.runtimeInfoFuture});

@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);

return Container(
width: double.infinity,
padding: const EdgeInsets.all(18),
decoration: BoxDecoration(
color: const Color(0xffF8F5F1),
borderRadius: BorderRadius.circular(18),
border: Border.all(color: const Color(0xffE7DED3)),
),
child: FutureBuilder<RuntimeInfo?>(
future: runtimeInfoFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: SizedBox(
width: 22,
height: 22,
child: CircularProgressIndicator(strokeWidth: 2.4),
),
);
}
final info = snapshot.data;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
l10n.aboutRuntimeTitle,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w700,
color: Color(0xff4E4338),
),
),
const SizedBox(height: 14),
_BundleInfoRow(
label: l10n.aboutRuntimeAppLabel,
value: info?.appVersion ?? '—',
),
const SizedBox(height: 10),
_BundleInfoRow(
label: l10n.aboutRuntimeLitertlmLabel,
value: info?.litertlmVersion ?? '—',
),
const SizedBox(height: 10),
_BundleInfoRow(
label: l10n.aboutRuntimeBackendLabel,
value: info?.llmBackend ?? '—',
),
],
);
},
Expand Down
Loading