Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stage v0.4.0 #5

Merged
merged 4 commits into from
Aug 23, 2024
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
6 changes: 5 additions & 1 deletion confichat/.idea/workspace.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions confichat/lib/app_data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import 'package:confichat/factories.dart';


typedef CallbackSwitchProvider = Function(AiProvider?);
typedef CallbackUpdateSession = Future<void> Function(Directory chatSessionsDir, String modelName, String filename, EncryptionPayload encryptionPayload);

class AppData {

// Singleton setup
Expand Down Expand Up @@ -49,6 +51,7 @@ class AppData {
double windowHeight = 1024;
AiProvider defaultProvider = AiProvider.ollama;
String rootPath = '';
CallbackUpdateSession? callbackUpdateSession;

void defaultCallback(AiProvider? provider) {
}
Expand Down Expand Up @@ -104,6 +107,13 @@ enum UserDeviceType {
tablet
}

class EncryptionPayload {
final String base64IV;
final String encryptedData;

EncryptionPayload(this.base64IV, this.encryptedData);
}

class ModelItem {
final String id;
final String name;
Expand Down
120 changes: 97 additions & 23 deletions confichat/lib/persistent_storage.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class PersistentStorage {
}

// Create a chat session file in json format
static Future<void> saveFile(Directory dir, String modelName, String fileName, String content, bool isEncrypted, String encryptionIV) async {
static Future<void> saveFile(Directory dir, String modelName, String fileName, String content, String encryptionIV) async {
// Create directory if it doesn't exist
String folder = cleanupModelName(modelName);
final directory = Directory('${dir.path}/$folder');
Expand Down Expand Up @@ -83,6 +83,47 @@ class PersistentStorage {
}
}

static Future<EncryptionPayload> getEncryptionPayload(Directory dir, String modelName, String fileName) async {

try {

// Read the file from disk
String folder = cleanupModelName(modelName);
final file = File('${dir.path}/$folder/${AppData.appFilenameBookend}$fileName${AppData.appFilenameBookend}.json');
final fileContent = await file.readAsString();

// Decode json and extract metadata
final jsonData = jsonDecode(fileContent);
final options = jsonData['options'];
final messages = jsonData['messages'];
final encryptionIV = options['encryptionIV'] ?? '';

// Check for encryption IV which indicates this session has been encrypted
if(encryptionIV == null || messages == null || messages.isEmpty) { return EncryptionPayload('', '');}

// Retrieve first message
List<Map<String, dynamic>> chatData = List<Map<String, dynamic>>.from(jsonDecode(messages));
String firstUserMessage = '';
for (var message in chatData) {
if (message.containsKey('role') && message['role'] == 'user') {
firstUserMessage = message['content'] as String;
break;
}
}

// No message found
if(firstUserMessage.isEmpty) { return EncryptionPayload('', '');}

return EncryptionPayload(encryptionIV as String, firstUserMessage) ;

} catch (e) {
if (kDebugMode) {print('Error reading encryptionIV: $e');}
}

return EncryptionPayload('', '');

}

// Retrieve encrypted chat session file
static Future<String> readJsonFile(Directory dir, String modelName, String fileName) async {
try {
Expand Down Expand Up @@ -239,6 +280,22 @@ class PersistentStorage {

class CryptoUtils {

static bool testKey({
required EncryptionPayload encryptionPayload,
required String userKey,
}) {
try{
String decrypted = decryptString(
base64IV: encryptionPayload.base64IV,
userKey: userKey,
encryptedData: encryptionPayload.encryptedData);
return decrypted.isNotEmpty;
} catch (e)
{
return false;
}
}

static String encryptStringIV({
required String base64IV,
required String userKey,
Expand Down Expand Up @@ -267,8 +324,7 @@ static String encryptStringIV({

return outBase64Data;
} catch (e) {
// Handle or rethrow the exception as needed
throw Exception('Encryption failed: $e');
throw Exception('Encryption failed: $e');
}

}
Expand Down Expand Up @@ -302,7 +358,17 @@ static String encryptStringIV({

}

static String encryptChatData({
static void encryptChatDataWithIV({
required String base64IV,
required String userKey,
required List<Map<String, dynamic>> chatData,
}) {
final iv = encrypt.IV.fromBase64(base64IV);
encryptChatData(iv: iv, userKey: userKey, chatData: chatData);
}


static String encryptChatDataGenerateIV({
required String userKey,
required List<Map<String, dynamic>> chatData,
}) {
Expand All @@ -311,6 +377,16 @@ static String encryptStringIV({
final iv = encrypt.IV.fromLength(16);
final base64IV = iv.base64;

encryptChatData(iv: iv, userKey: userKey, chatData: chatData);
return base64IV;
}

static void encryptChatData({
required encrypt.IV iv,
required String userKey,
required List<Map<String, dynamic>> chatData,
}) {

for (var entry in chatData) {
// Encrypt content
if (entry['content'] != null) {
Expand All @@ -322,28 +398,26 @@ static String encryptStringIV({
entry['content'] = encryptedContent;
}

// Encrypt images if they exist
if (entry['images'] != null && entry['images'] is List) {
entry['images'] = (entry['images'] as List).map((image) {
if (image is String) {
final encryptedImage = CryptoUtils.encryptString(
iv: iv,
userKey: userKey,
data: image,
);
return encryptedImage;
} else {
if (kDebugMode) { print('Warning: Non-string image data encountered'); }
return null;
}
}).whereType<String>().toList();
// Encrypt images if they exist
if (entry['images'] != null && entry['images'] is List) {
entry['images'] = (entry['images'] as List).map((image) {
if (image is String) {
final encryptedImage = CryptoUtils.encryptString(
iv: iv,
userKey: userKey,
data: image,
);
return encryptedImage;
} else {
if (kDebugMode) { print('Warning: Non-string image data encountered'); }
return null;
}
}).whereType<String>().toList();
}
}

}

return base64IV;
}

static void decryptChatData({
required String base64IV,
required String userKey,
Expand Down Expand Up @@ -385,7 +459,7 @@ static String encryptStringIV({
}
}

static void decryptToChatData({
static void decryptToChatData({
required String base64IV,
required String userKey,
required dynamic jsonData,
Expand Down
1 change: 1 addition & 0 deletions confichat/lib/ui_add_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ class AddModelDialogState extends State<AddModelDialog> {

child: SingleChildScrollView(
scrollDirection: Axis.vertical,
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,

child: SizedBox(width: 400, height: 300,
child: Column(
Expand Down
42 changes: 25 additions & 17 deletions confichat/lib/ui_advanced_options.dart
Original file line number Diff line number Diff line change
Expand Up @@ -93,23 +93,26 @@ class AdvancedOptionsState extends State<AdvancedOptions> {
const SizedBox(height: 24),

ConstrainedBox(
constraints: const BoxConstraints(
maxHeight: 500.0,
),
constraints: BoxConstraints(
maxHeight: AppData.instance.getUserDeviceType(context) == UserDeviceType.phone ? 250.0 : 500, ),
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
child: Column(
children: [

const Text('Scroll to see more tuning options', style: TextStyle(fontStyle: FontStyle.italic)),
const SizedBox(height: 16),

_buildSliderWithTooltip(
label: 'Temperature',
value: temperature,
min: 0.0,
max: 2.0,
divisions: 20,
onChanged: (value) => setState(() => temperature = value),
description: 'Controls the randomness of the output. Lower values make responses more focused and deterministic, while higher values increase variability and creativity.',
whatItMeans: 'Use lower temperatures for precise and predictable outputs and higher values for more creative and varied responses.',
description: 'Controls the randomness of the output.',
whatItMeans: 'Use lower temperatures (e.g., 0.2) for tasks that require precision and predictable outcomes, such as technical writing or code generation. Use higher temperatures (e.g., 1.5) for more open-ended tasks like creative writing or brainstorming.',
),
const SizedBox(height: 8),

Expand All @@ -120,33 +123,37 @@ class AdvancedOptionsState extends State<AdvancedOptions> {
max: 1.0,
divisions: 10,
onChanged: (value) => setState(() => probability = value),
description: 'Determines the diversity of the response by sampling from the top tokens. Lower values focus on the most likely tokens, while higher values consider a wider range of possibilities.',
whatItMeans: 'Set lower values for predictable outputs and higher values for more diverse and creative responses.',
description: 'Determines the diversity of the response.',
whatItMeans: 'Lower values (e.g., 0.3) result in more focused and predictable outputs by limiting the model to a smaller subset of the most probable tokens. Higher values (e.g., 0.9) allow for a wider variety of tokens, producing more creative and less predictable responses.',
),

_buildTextInputWithTooltip(
label: 'Max Tokens',
controller: maxTokensController,
onChanged: (value) => setState(() => maxTokens = int.tryParse(value) ?? maxTokens),
description: 'Sets the maximum number of tokens that the model will generate in the response. Limits response length for concise or detailed answers.',
description: 'Sets the maximum number of tokens that the model will generate in the response.',
whatItMeans: 'Limits response length. Use lower values for concise answers and higher values for detailed responses.',
),
_buildTextInputWithTooltip(
label: 'Stop Sequences',
controller: stopSequenceController,
onChanged: (value) { setState(() {
widget.api.stopSequences = value.split(',').map((s) => s.trim()).toList();}); },
description: 'Specifies one or more sequences where the model should stop generating text. Enter multiple sequences separated by commas.',
whatItMeans: 'Define where the response should end to prevent overly lengthy or unnecessary outputs. (example: .,\\n, user:)',
),

_buildTextInputWithTooltip(
label: 'System Prompt',
controller: systemPromptController,
isEnabled: widget.enableSystemPrompt,
maxLines: 5,
onChanged: (value) { setState(() {widget.api.systemPrompt = value;}); },
description: 'Initial instruction that sets the context or role for the entire conversation. It influences how the model understands and responds to user inputs by defining its persona, tone, or task.',
description: 'Initial instruction that sets the context or role for the entire conversation.',
whatItMeans: 'By setting the model\'s role or focus, such as acting as a helpful assistant, technical expert, or creative writer, it aligns the model\'s responses to user expectations and the intended application',
),

_buildTextInputWithTooltip(
label: 'Stop Sequences',
controller: stopSequenceController,
onChanged: (value) { setState(() {
widget.api.stopSequences = value.split(',').map((s) => s.trim()).toList();}); },
description: 'Enter multiple sequences separated by commas.',
whatItMeans: 'Define where the response should end to prevent overly lengthy or unnecessary outputs. (example: .,\\n, user:)',
),

] )
),
),
Expand Down Expand Up @@ -310,6 +317,7 @@ class AdvancedOptionsState extends State<AdvancedOptions> {
widget.api.probability = probability;
widget.api.maxTokens = maxTokens;
widget.api.stopSequences = stopSequenceController.text.split(',').map((s) => s.trim()).toList();
widget.api.systemPrompt = systemPromptController.text;
}

}
4 changes: 3 additions & 1 deletion confichat/lib/ui_app_settings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,9 @@ class AppSettingsState extends State<AppSettings> {
const SizedBox(height: 18),

SingleChildScrollView(
scrollDirection: Axis.vertical, child: Column ( children: [
scrollDirection: Axis.vertical,
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
child: Column ( children: [

// Clear messages
SwitchListTile(
Expand Down
Loading