Skip to content

Commit a66a4e0

Browse files
authored
Merge pull request #147 from DenisovAV/feature/builder-validation-tdd
Feature/builder validation tdd
2 parents db3357c + 3525296 commit a66a4e0

File tree

216 files changed

+227625
-3754
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

216 files changed

+227625
-3754
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.11.13
2+
-**iOS Embeddings Fix**: XNNPACK + SentencePiece integration for better results on iOS
3+
- 🌐 **Web CDN**: Modules available via jsDelivr (`@0.11.13/web/*.js`)
4+
15
## 0.11.12
26
- 🌐 **Web VectorStore**: Full RAG support on web with SQLite WASM
37
- Uses wa-sqlite with OPFS storage (10x faster than IndexedDB)

android/src/main/kotlin/dev/flutterberlin/flutter_gemma/EmbeddingModel.kt

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package dev.flutterberlin.flutter_gemma
22

33
import android.content.Context
4+
import android.os.Build
45
import com.google.ai.edge.localagents.rag.models.EmbedData
56
import com.google.ai.edge.localagents.rag.models.EmbeddingRequest
67
import com.google.ai.edge.localagents.rag.models.GemmaEmbeddingModel
@@ -28,19 +29,34 @@ class EmbeddingModel(
2829
if (!modelFile.exists()) {
2930
throw IllegalArgumentException("Model file not found: $modelPath")
3031
}
31-
32+
3233
val tokenizerFile = File(tokenizerPath)
3334
if (!tokenizerFile.exists()) {
3435
throw IllegalArgumentException("Tokenizer file not found: $tokenizerPath")
3536
}
36-
37+
38+
// Auto-detect: Force CPU on emulator (no GPU/OpenCL support)
39+
val effectiveUseGPU = if (useGPU && isEmulator()) {
40+
android.util.Log.i("EmbeddingModel", "Emulator detected, forcing CPU backend")
41+
false
42+
} else {
43+
useGPU
44+
}
45+
3746
// Initialize the new GemmaEmbeddingModel from RAG library
3847
gemmaEmbeddingModel = GemmaEmbeddingModel(
3948
modelPath,
4049
tokenizerPath,
41-
useGPU
50+
effectiveUseGPU
4251
)
4352
}
53+
54+
private fun isEmulator(): Boolean {
55+
return Build.FINGERPRINT.startsWith("generic")
56+
|| Build.MODEL.contains("Emulator")
57+
|| Build.MODEL.contains("Android SDK built for x86")
58+
|| Build.PRODUCT.contains("sdk")
59+
}
4460

4561
fun embed(text: String): List<Double> {
4662
val model = gemmaEmbeddingModel ?: throw IllegalStateException("Tokenizer not initialized")

example/ios/Podfile

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,19 @@ end
3939
post_install do |installer|
4040
installer.pods_project.targets.each do |target|
4141
flutter_additional_ios_build_settings(target)
42+
43+
# Force load TensorFlowLiteSelectTfOps for embedding models
44+
if target.name == 'Runner'
45+
target.build_configurations.each do |config|
46+
# Only apply to device builds, not simulator
47+
sdk = config.build_settings['SDKROOT']
48+
if sdk.nil? || !sdk.include?('simulator')
49+
config.build_settings['OTHER_LDFLAGS'] ||= ['$(inherited)']
50+
config.build_settings['OTHER_LDFLAGS'] << '-force_load'
51+
config.build_settings['OTHER_LDFLAGS'] << '$(PODS_ROOT)/TensorFlowLiteSelectTfOps/Frameworks/TensorFlowLiteSelectTfOps.xcframework/ios-arm64/TensorFlowLiteSelectTfOps.framework/TensorFlowLiteSelectTfOps'
52+
end
53+
end
54+
end
4255
end
4356
end
4457

example/ios/Podfile.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ PODS:
22
- background_downloader (0.0.1):
33
- Flutter
44
- Flutter (1.0.0)
5-
- flutter_gemma (0.11.10):
5+
- flutter_gemma (0.11.12):
66
- Flutter
77
- MediaPipeTasksGenAI (= 0.10.24)
88
- MediaPipeTasksGenAIC (= 0.10.24)
@@ -79,7 +79,7 @@ EXTERNAL SOURCES:
7979
SPEC CHECKSUMS:
8080
background_downloader: 50e91d979067b82081aba359d7d916b3ba5fadad
8181
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
82-
flutter_gemma: 87769bb3e080627eeddb04d3a1007dd0c0fa623d
82+
flutter_gemma: dc4a0a5e6bdba4cf05655c08f0fefd552022968a
8383
image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
8484
integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e
8585
large_file_handler: b37481e9b4972562ffcdc8f75700f47cd592bcec

example/lib/embedding_test_screen.dart

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'package:flutter_gemma_example/models/base_model.dart';
66
import 'package:flutter_gemma_example/models/embedding_model.dart' as example_embedding_model;
77
import 'package:flutter_gemma_example/services/auth_token_service.dart';
88
import 'package:flutter_gemma_example/cosine_similarity_screen.dart';
9+
import 'package:flutter_gemma_example/rag_demo_screen.dart';
910

1011
class EmbeddingTestScreen extends StatefulWidget {
1112
final example_embedding_model.EmbeddingModel model;
@@ -231,6 +232,36 @@ class _EmbeddingTestScreenState extends State<EmbeddingTestScreen> {
231232
],
232233
),
233234

235+
const SizedBox(height: 12),
236+
237+
Row(
238+
children: [
239+
Expanded(
240+
child: ElevatedButton(
241+
onPressed: _navigateToCosineSimilarity,
242+
style: ElevatedButton.styleFrom(
243+
backgroundColor: const Color(0xFF2a5a8c),
244+
foregroundColor: Colors.white,
245+
padding: const EdgeInsets.symmetric(vertical: 12),
246+
),
247+
child: const Text('Cosine Similarity'),
248+
),
249+
),
250+
const SizedBox(width: 12),
251+
Expanded(
252+
child: ElevatedButton(
253+
onPressed: _navigateToVectorStore,
254+
style: ElevatedButton.styleFrom(
255+
backgroundColor: const Color(0xFF2a5a8c),
256+
foregroundColor: Colors.white,
257+
padding: const EdgeInsets.symmetric(vertical: 12),
258+
),
259+
child: const Text('VectorStore RAG'),
260+
),
261+
),
262+
],
263+
),
264+
234265
const SizedBox(height: 24),
235266

236267
// Results section placeholder
@@ -488,4 +519,13 @@ class _EmbeddingTestScreenState extends State<EmbeddingTestScreen> {
488519
),
489520
);
490521
}
522+
523+
void _navigateToVectorStore() {
524+
Navigator.push(
525+
context,
526+
MaterialPageRoute<void>(
527+
builder: (context) => const RagDemoScreen(),
528+
),
529+
);
530+
}
491531
}

example/lib/integration_test_screen.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import 'package:flutter_gemma/mobile/flutter_gemma_mobile.dart' as legacy_mobile
88
import 'package:path_provider/path_provider.dart';
99
import 'package:background_downloader/background_downloader.dart';
1010
import 'package:flutter_gemma_example/utils/test_preferences.dart';
11-
import 'package:flutter_gemma_example/vector_store_test_screen.dart';
11+
import 'package:flutter_gemma_example/rag_demo_screen.dart';
1212
import 'package:shared_preferences/shared_preferences.dart';
1313
import 'dart:io';
1414
import 'dart:async';
@@ -3117,15 +3117,15 @@ class _IntegrationTestScreenState extends State<IntegrationTestScreen> {
31173117

31183118
// === SECTION 5: VectorStore Tests ===
31193119
_buildSection(
3120-
title: 'VectorStore Tests (v0.11.10)',
3120+
title: 'RAG Demo',
31213121
children: [
31223122
_buildButton(
3123-
'Run VectorStore Tests',
3123+
'Open RAG Demo',
31243124
() {
31253125
Navigator.push(
31263126
context,
31273127
MaterialPageRoute<void>(
3128-
builder: (context) => const VectorStoreTestScreen(),
3128+
builder: (context) => const RagDemoScreen(),
31293129
),
31303130
);
31313131
},

example/lib/main.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ void main() async {
66
WidgetsFlutterBinding.ensureInitialized();
77

88
// Initialize Flutter Gemma (required)
9-
await FlutterGemma.initialize(enableWebCache: false);
9+
await FlutterGemma.initialize();
1010

1111
runApp(const ChatApp());
1212
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/// Sample documents for RAG demo
2+
const List<Map<String, String>> sampleDocuments = [
3+
{
4+
'id': 'flutter_intro',
5+
'content': 'Flutter is an open-source UI framework by Google for building natively compiled applications for mobile, web, and desktop from a single codebase.',
6+
},
7+
{
8+
'id': 'dart_language',
9+
'content': 'Dart is a client-optimized programming language for fast apps on multiple platforms. It is developed by Google and used to build Flutter applications.',
10+
},
11+
{
12+
'id': 'flutter_widgets',
13+
'content': 'In Flutter, everything is a widget. Widgets describe what their view should look like given their current configuration and state.',
14+
},
15+
{
16+
'id': 'flutter_hot_reload',
17+
'content': 'Flutter hot reload helps you quickly experiment, build UIs, add features, and fix bugs by injecting updated source code into the running Dart VM.',
18+
},
19+
{
20+
'id': 'flutter_state',
21+
'content': 'Flutter uses setState() for simple state management in StatefulWidget. For complex apps, consider using Provider, Riverpod, or BLoC pattern.',
22+
},
23+
{
24+
'id': 'flutter_platforms',
25+
'content': 'Flutter supports iOS, Android, web, Windows, macOS, and Linux platforms, allowing developers to create cross-platform applications efficiently.',
26+
},
27+
{
28+
'id': 'dart_null_safety',
29+
'content': 'Dart null safety helps catch null reference errors at compile time. Use nullable types with ? and null-aware operators like ?? and ?. for safer code.',
30+
},
31+
{
32+
'id': 'flutter_performance',
33+
'content': 'Flutter achieves high performance by compiling to native ARM code and using Skia graphics engine for rendering at 60fps or higher.',
34+
},
35+
];
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import 'package:flutter/material.dart';
2+
import '../rag_demo_data.dart';
3+
import 'sample_documents_screen.dart';
4+
5+
class KnowledgeBaseSection extends StatelessWidget {
6+
final bool isLoading;
7+
final int addTimeMs;
8+
final VoidCallback onAddDocuments;
9+
final VoidCallback onClearDocuments;
10+
11+
const KnowledgeBaseSection({
12+
super.key,
13+
required this.isLoading,
14+
required this.addTimeMs,
15+
required this.onAddDocuments,
16+
required this.onClearDocuments,
17+
});
18+
19+
@override
20+
Widget build(BuildContext context) {
21+
return Column(
22+
crossAxisAlignment: CrossAxisAlignment.start,
23+
children: [
24+
const Text(
25+
'Knowledge Base',
26+
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
27+
),
28+
const SizedBox(height: 12),
29+
30+
// Buttons
31+
Row(
32+
children: [
33+
Expanded(
34+
child: ElevatedButton.icon(
35+
onPressed: isLoading
36+
? null
37+
: () {
38+
Navigator.push(
39+
context,
40+
MaterialPageRoute<void>(
41+
builder: (context) => SampleDocumentsScreen(
42+
onAddDocuments: onAddDocuments,
43+
isLoading: isLoading,
44+
),
45+
),
46+
);
47+
},
48+
icon: const Icon(Icons.add),
49+
label: Text('Add ${sampleDocuments.length} Docs'),
50+
),
51+
),
52+
const SizedBox(width: 8),
53+
Expanded(
54+
child: ElevatedButton.icon(
55+
onPressed: isLoading ? null : onClearDocuments,
56+
icon: const Icon(Icons.delete),
57+
label: const Text('Clear All'),
58+
style: ElevatedButton.styleFrom(
59+
backgroundColor: Colors.red.shade900,
60+
foregroundColor: Colors.white,
61+
),
62+
),
63+
),
64+
],
65+
),
66+
if (addTimeMs > 0)
67+
Padding(
68+
padding: const EdgeInsets.only(top: 4),
69+
child: Text(
70+
'Last add: ${addTimeMs}ms',
71+
style: const TextStyle(fontSize: 12, color: Colors.grey),
72+
),
73+
),
74+
],
75+
);
76+
}
77+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:flutter_gemma/flutter_gemma.dart';
3+
4+
class ResultCard extends StatelessWidget {
5+
final RetrievalResult result;
6+
7+
const ResultCard({
8+
super.key,
9+
required this.result,
10+
});
11+
12+
Color _getScoreColor(double score) {
13+
if (score >= 0.8) return Colors.green;
14+
if (score >= 0.6) return Colors.lightGreen;
15+
if (score >= 0.4) return Colors.orange;
16+
return Colors.red;
17+
}
18+
19+
@override
20+
Widget build(BuildContext context) {
21+
return Card(
22+
margin: const EdgeInsets.only(bottom: 8),
23+
child: Padding(
24+
padding: const EdgeInsets.all(12),
25+
child: Column(
26+
crossAxisAlignment: CrossAxisAlignment.start,
27+
children: [
28+
Row(
29+
children: [
30+
Container(
31+
padding: const EdgeInsets.symmetric(
32+
horizontal: 8,
33+
vertical: 4,
34+
),
35+
decoration: BoxDecoration(
36+
color: _getScoreColor(result.similarity),
37+
borderRadius: BorderRadius.circular(12),
38+
),
39+
child: Text(
40+
'${(result.similarity * 100).toStringAsFixed(1)}%',
41+
style: const TextStyle(
42+
color: Colors.white,
43+
fontWeight: FontWeight.bold,
44+
fontSize: 12,
45+
),
46+
),
47+
),
48+
const SizedBox(width: 8),
49+
Expanded(
50+
child: Text(
51+
result.id,
52+
style: const TextStyle(
53+
fontWeight: FontWeight.bold,
54+
fontSize: 12,
55+
color: Colors.grey,
56+
),
57+
),
58+
),
59+
],
60+
),
61+
const SizedBox(height: 8),
62+
Text(
63+
result.content,
64+
style: const TextStyle(fontSize: 14),
65+
),
66+
],
67+
),
68+
),
69+
);
70+
}
71+
}

0 commit comments

Comments
 (0)