| 844 | } |
| 845 | |
| 846 | void mitk::nnInteractiveTool::ConstructRemoteSession() |
| 847 | { |
| 848 | auto prefs = GetPreferences(); |
| 849 | const auto serverUrl = prefs->Get("nnInteractive/serverUrl", ""); |
| 850 | const auto apiKey = prefs->Get("nnInteractive/apiKey", ""); |
| 851 | |
| 852 | if (serverUrl.empty()) |
| 853 | mitkThrow() << "Remote mode is selected but no server URL is configured. " |
| 854 | "Click on Settings to configure a server URL."; |
| 855 | |
| 856 | m_Impl->ResetBackend(); |
| 857 | |
| 858 | auto pythonContext = m_Impl->GetPythonContext(); |
| 859 | |
| 860 | // Claim a session on the server. Map the expected failure modes to short, |
| 861 | // user-facing messages (reported via the nni_connect_error variable) instead |
| 862 | // of letting an httpx/Python traceback bubble up to the GUI. The server URL is |
| 863 | // shown credential-stripped (nni_server_display), and the API key is cleared in |
| 864 | // a finally so it never lingers in the shared dictionary even if a statement |
| 865 | // here throws. |
| 866 | { |
| 867 | std::ostringstream pyCommands; pyCommands |
| 868 | << "nni_server_url = " << PyQuote(serverUrl) << "\n" |
| 869 | << "nni_server_display = " << PyQuote(SanitizeUrlForDisplay(serverUrl)) << "\n" |
| 870 | << "nni_api_key = " << (apiKey.empty() ? std::string("None") : PyQuote(apiKey)) << "\n" |
| 871 | << "nni_connect_error = ''\n" |
| 872 | << "try:\n" |
| 873 | << " from nnInteractive.inference.remote import (\n" |
| 874 | << " nnInteractiveRemoteInferenceSession, ServerAtCapacityError, SessionExpiredError)\n" |
| 875 | << " import httpx\n" |
| 876 | << " try:\n" |
| 877 | << " session = nnInteractiveRemoteInferenceSession(server_url=nni_server_url, api_key=nni_api_key)\n" |
| 878 | << " except ServerAtCapacityError:\n" |
| 879 | << " nni_connect_error = 'The nnInteractive server is at capacity. Please try again later.'\n" |
| 880 | << " except SessionExpiredError:\n" |
| 881 | << " nni_connect_error = 'The nnInteractive server rejected the session request. Please try again.'\n" |
| 882 | << " except httpx.HTTPStatusError as _e:\n" |
| 883 | << " if _e.response.status_code == 401:\n" |
| 884 | << " nni_connect_error = 'The nnInteractive server rejected the API key. Check the API key in the nnInteractive preferences.'\n" |
| 885 | << " elif 'text/html' in _e.response.headers.get('content-type', ''):\n" |
| 886 | << " nni_connect_error = (f'The server at {nni_server_display} returned an HTML page instead of a response. '\n" |
| 887 | << " 'An HTTP proxy may be intercepting the request; try adding the host to NO_PROXY.')\n" |
| 888 | << " else:\n" |
| 889 | << " nni_connect_error = f'The nnInteractive server returned an error (HTTP {_e.response.status_code}).'\n" |
| 890 | << " except (httpx.ConnectError, httpx.ConnectTimeout):\n" |
| 891 | << " nni_connect_error = f'Could not reach the nnInteractive server at {nni_server_display}. Check the server URL and make sure the server is running.'\n" |
| 892 | << " except httpx.HTTPError as _e:\n" |
| 893 | << " nni_connect_error = f'Could not connect to the nnInteractive server at {nni_server_display}: {_e}'\n" |
| 894 | << " except (ValueError, KeyError):\n" |
| 895 | << " nni_connect_error = ('The nnInteractive server returned an unexpected response. '\n" |
| 896 | << " 'An HTTP proxy or captive portal may be intercepting the request; '\n" |
| 897 | << " 'check the server URL and NO_PROXY.')\n" |
| 898 | << "except ImportError:\n" |
| 899 | << " nni_connect_error = ('The installed nnInteractive does not support remote mode. '\n" |
| 900 | << " 'Reinstall or upgrade nnInteractive.')\n" |
| 901 | << "finally:\n" |
| 902 | << " del nni_api_key\n"; |
| 903 | pythonContext->Execute(pyCommands.str()); |
no test coverage detected