diff --git a/gai-frontend/lib/chat/chat.dart b/gai-frontend/lib/chat/chat.dart index 6e17a0d90..50238cd9d 100644 --- a/gai-frontend/lib/chat/chat.dart +++ b/gai-frontend/lib/chat/chat.dart @@ -2,6 +2,7 @@ import 'package:orchid/api/orchid_eth/chains.dart'; import 'package:orchid/api/orchid_eth/orchid_account.dart'; import 'package:orchid/api/orchid_eth/orchid_account_detail.dart'; import 'package:orchid/api/orchid_keys.dart'; +import 'package:orchid/api/orchid_user_config/orchid_user_param.dart'; import 'package:orchid/chat/api/user_preferences_chat.dart'; import 'package:orchid/chat/model.dart'; import 'package:orchid/chat/provider_connection.dart'; @@ -67,6 +68,7 @@ class _ChatViewState extends State { // AuthTokenMethod _authTokenMethod = AuthTokenMethod.manual; String? _authToken; String? _inferenceUrl; + String? _scriptURLParam; @override void initState() { @@ -89,20 +91,39 @@ class _ChatViewState extends State { log('Error initializing from params: $e, $stack'); } + // Initialize the scripting extension mechanism + _initScripting(); + } + + void _initScripting() { // final script = UserPreferencesScripts().userScript.get(); // log('User script on start: $script'); - // Initialize the scripting extension mechanism ChatScripting.init( + // If a script URL is provided, it will be loaded. // url: 'lib/extensions/filter_example.js', + url: _scriptURLParam, + // If debugMode is true, the script will be re-loaded before each invocation // debugMode: true, - script: UserPreferencesScripts().userScript.get(), - // Not: script overrides url + + // Allow a provided script url param to override the stored script + script: _scriptURLParam == null + ? UserPreferencesScripts().userScript.get() + : null, + providerManager: _providerManager, modelManager: _modelManager, getUserSelectedModels: () => _userSelectedModels, chatHistory: _chatHistory, addChatMessageToUI: _addChatMessage, + onScriptError: (error) { + _addMessage(ChatMessageSource.internal, + 'User Script error: ${error.truncate(128)}'); + _addMessage(ChatMessageSource.system, 'User Script error (see logs).'); + }, + onScriptLoaded: (msg) { + _addMessage(ChatMessageSource.system, msg); + }, ); } @@ -164,23 +185,17 @@ class _ChatViewState extends State { // Init from user parameters (for web) void _initFromParams() { - Map params = Uri.base.queryParameters; - try { - _funder = EthereumAddress.from(params['funder'] ?? ''); - } catch (e) { - _funder = null; - } - try { - _signerKey = BigInt.parse(params['signer'] ?? ''); - } catch (e) { - _signerKey = null; - } + final params = OrchidUserParams(); + _funder = params.getEthereumAddress('funder'); + _signerKey = params.getBigInt('signer'); _accountChanged(); - String? provider = params['provider']; + String? provider = params.get('provider'); if (provider != null) { _providerManager.setUserProvider(provider); } + + _scriptURLParam = params.getURL('script'); } void providerConnected([name = '']) { @@ -342,8 +357,7 @@ class _ChatViewState extends State { // FocusManager.instance.primaryFocus?.unfocus(); // ? // If we have a script selected allow it to handle the prompt - if (ChatScripting.enabled && - (UserPreferencesScripts().userScriptEnabled.get() ?? false)) { + if (ChatScripting.enabled) { ChatScripting.instance.sendUserPrompt(msg, _userSelectedModels); } else { _sendUserPromptDefaultBehavior(msg); diff --git a/gai-frontend/lib/chat/scripting/chat_scripting.dart b/gai-frontend/lib/chat/scripting/chat_scripting.dart index b4f3606da..260061991 100644 --- a/gai-frontend/lib/chat/scripting/chat_scripting.dart +++ b/gai-frontend/lib/chat/scripting/chat_scripting.dart @@ -1,3 +1,4 @@ +import 'package:orchid/chat/api/user_preferences_chat.dart'; import 'package:orchid/chat/chat_history.dart'; import 'package:orchid/chat/chat_message.dart'; import 'package:orchid/chat/model.dart'; @@ -22,15 +23,16 @@ class ChatScripting { return _instance!; } - static bool get enabled => _instance != null; - // Scripting State + String? url; String? script; late ProviderManager providerManager; late ModelManager modelManager; late List Function() getUserSelectedModels; late ChatHistory chatHistory; late void Function(ChatMessage) addChatMessageToUI; + late void Function(String) onScriptError; + late void Function(String) onScriptLoaded; late bool debugMode; static Future init({ @@ -47,6 +49,8 @@ class ChatScripting { required ModelManager modelManager, required List Function() getUserSelectedModels, required Function(ChatMessage) addChatMessageToUI, + required void Function(String) onScriptError, + required void Function(String) onScriptLoaded, }) async { if (_instance != null) { throw Exception("ChatScripting already initialized!"); @@ -59,27 +63,55 @@ class ChatScripting { instance.modelManager = modelManager; instance.getUserSelectedModels = getUserSelectedModels; instance.addChatMessageToUI = addChatMessageToUI; + instance.onScriptError = onScriptError; + instance.onScriptLoaded = onScriptLoaded; if (url != null) { - // Install persistent callback functions - final script = await instance.loadScriptFromURL(url); - instance.setScript(script); + instance.setURL(url); } - if (script != null) { instance.setScript(script); } } + static bool get enabled { + return ChatScripting._instance != null + && (instance.url != null // assume enabled when URL provided as param + || (userPrefEnabled && instance.script != null)); + } + + static bool get userPrefEnabled { + return (UserPreferencesScripts().userScriptEnabled.get() ?? false); + } + + Future setURL(String newURL) async { + url = newURL; + final String script; + try { + script = await instance.loadScriptFromURL(url!); + onScriptLoaded("Script loaded from URL: $url"); + setScript(script); + } catch (e) { + log("Failed to load script from URL: $e"); + onScriptError(e.toString()); + } + } + // Set the script into the environment void setScript(String newScript) { - // init the global bindings once, when we have a script - if (script == null) { - addGlobalBindings(); + try { + // init the global bindings once, when we have a script + if (script == null) { + addGlobalBindings(); + } + script = newScript; + // Do one setup and evaluation of the script now + evalExtensionScript(); + } catch (e, stack) { + log("Failed to eval script: $e"); + log(stack.toString()); + onScriptError(e.toString()); } - script = newScript; - // Do one setup and evaluation of the script now - evalExtensionScript(); } Future loadScriptFromURL(String url) async { @@ -95,7 +127,8 @@ class ChatScripting { // If the result is HTML we have failed if (response.headers['content-type']!.contains('text/html')) { throw Exception( - "Failed to load script from $url: HTML response: ${response.body.truncate(64)}"); + "Failed to load script from $url: HTML response: ${response.body + .truncate(64)}"); } // log("Loaded script: $script"); @@ -110,6 +143,7 @@ class ChatScripting { throw Exception("No script to evaluate."); } evaluateJS(script!); // We could get a result back async here if needed + onScriptLoaded("Script installed."); } catch (e, stack) { log("Failed to evaluate script: $e"); log(stack.toString()); @@ -130,6 +164,10 @@ class ChatScripting { // If debug mode evaluate the script before each usage if (debugMode) { + // reload the URL in debug mode + if (url != null) { + setURL(url!); // reload the script from the URL + } evalExtensionScript(); } @@ -157,8 +195,8 @@ class ChatScripting { // Implementation of sendMessagesToModel callback function invoked from JS // Send a list of ChatMessage to a model for inference and return a promise of ChatMessageJS - JSPromise sendMessagesToModelJSImpl( - JSArray messagesJS, String modelId, int? maxTokens) { + JSPromise sendMessagesToModelJSImpl(JSArray messagesJS, String modelId, + int? maxTokens) { log("dart: Send messages to model called."); return (() async { @@ -197,7 +235,7 @@ class ChatScripting { .toJS; } - /// - /// END: JS callback implementations - /// +/// +/// END: JS callback implementations +/// } diff --git a/gai-frontend/lib/chat/scripting/code_viewer/scripts_menu_item.dart b/gai-frontend/lib/chat/scripting/code_viewer/scripts_menu_item.dart index 7b432d583..c6e14dde4 100644 --- a/gai-frontend/lib/chat/scripting/code_viewer/scripts_menu_item.dart +++ b/gai-frontend/lib/chat/scripting/code_viewer/scripts_menu_item.dart @@ -50,6 +50,7 @@ class ScriptsMenuItem extends StatelessWidget { return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ + // show one line of the script if (hasScript) Container( diff --git a/gui-orchid/lib/api/orchid_user_config/orchid_user_param.dart b/gui-orchid/lib/api/orchid_user_config/orchid_user_param.dart index 6b9857124..258629b3e 100644 --- a/gui-orchid/lib/api/orchid_user_config/orchid_user_param.dart +++ b/gui-orchid/lib/api/orchid_user_config/orchid_user_param.dart @@ -1,4 +1,5 @@ import 'dart:ui'; +import 'package:orchid/api/orchid_crypto.dart'; import 'package:orchid/api/orchid_log.dart'; import 'package:orchid/util/hex.dart'; @@ -43,6 +44,26 @@ class OrchidUserParams { } } + String? getURL(String name) { + return get(name); + } + + EthereumAddress? getEthereumAddress(String name) { + try { + return EthereumAddress.from(params[name] ?? ''); + } catch (e) { + return null; + } + } + + BigInt? getBigInt(String name) { + try { + return BigInt.parse(params[name] ?? ''); + } catch (e) { + return null; + } + } + bool has(String name) { return get(name) != null; }