mito-ai 0.1.41__py3-none-any.whl → 0.1.42__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of mito-ai might be problematic. Click here for more details.

Files changed (54) hide show
  1. mito_ai/__init__.py +7 -0
  2. mito_ai/_version.py +1 -1
  3. mito_ai/app_manager/__init__.py +4 -0
  4. mito_ai/app_manager/handlers.py +129 -0
  5. mito_ai/app_manager/models.py +58 -0
  6. mito_ai/completions/completion_handlers/agent_execution_handler.py +1 -1
  7. mito_ai/completions/completion_handlers/chat_completion_handler.py +2 -2
  8. mito_ai/completions/completion_handlers/utils.py +77 -37
  9. mito_ai/completions/models.py +2 -0
  10. mito_ai/completions/prompt_builders/utils.py +7 -0
  11. mito_ai/tests/completions/completion_handlers_utils_test.py +51 -0
  12. {mito_ai-0.1.41.data → mito_ai-0.1.42.data}/data/share/jupyter/labextensions/mito_ai/build_log.json +100 -100
  13. {mito_ai-0.1.41.data → mito_ai-0.1.42.data}/data/share/jupyter/labextensions/mito_ai/package.json +2 -2
  14. {mito_ai-0.1.41.data → mito_ai-0.1.42.data}/data/share/jupyter/labextensions/mito_ai/schemas/mito_ai/package.json.orig +1 -1
  15. mito_ai-0.1.41.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.01a962c68c8fae380f30.js → mito_ai-0.1.42.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.a9a35b6fcc54a7bcb32c.js +807 -44
  16. mito_ai-0.1.42.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.a9a35b6fcc54a7bcb32c.js.map +1 -0
  17. mito_ai-0.1.42.data/data/share/jupyter/labextensions/mito_ai/static/node_modules_process_browser_js.4b128e94d31a81ebd209.js +198 -0
  18. mito_ai-0.1.42.data/data/share/jupyter/labextensions/mito_ai/static/node_modules_process_browser_js.4b128e94d31a81ebd209.js.map +1 -0
  19. mito_ai-0.1.41.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.9a70f033717ba8689564.js → mito_ai-0.1.42.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.c7d9d8635826165de52e.js +23 -23
  20. mito_ai-0.1.42.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.c7d9d8635826165de52e.js.map +1 -0
  21. mito_ai-0.1.42.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_auth_dist_esm_providers_cognito_apis_signOut_mjs-node_module-75790d.688c25857e7b81b1740f.js +533 -0
  22. mito_ai-0.1.42.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_auth_dist_esm_providers_cognito_apis_signOut_mjs-node_module-75790d.688c25857e7b81b1740f.js.map +1 -0
  23. mito_ai-0.1.41.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_auth_dist_esm_providers_cognito_tokenProvider_tokenProvider_mjs.16430abf3466c3153f59.js → mito_ai-0.1.42.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_auth_dist_esm_providers_cognito_tokenProvider_tokenProvider_-72f1c8.a917210f057fcfe224ad.js +2977 -610
  24. mito_ai-0.1.42.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_auth_dist_esm_providers_cognito_tokenProvider_tokenProvider_-72f1c8.a917210f057fcfe224ad.js.map +1 -0
  25. mito_ai-0.1.41.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_ui-react_dist_esm_index_mjs.61289bff0db44828605b.js → mito_ai-0.1.42.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_ui-react_dist_esm_index_mjs.4fcecd65bef9e9847609.js +1 -481
  26. mito_ai-0.1.42.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_ui-react_dist_esm_index_mjs.4fcecd65bef9e9847609.js.map +1 -0
  27. mito_ai-0.1.41.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_core_dist_esm_singleton_apis_fetchAuthSession_mjs-node_modul-758875.dc495fd682071d97070c.js → mito_ai-0.1.42.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_react-dom_client_js-node_modules_aws-amplify_ui-react_dist_styles_css.b43d4249e4d3dac9ad7b.js +2 -60
  28. mito_ai-0.1.42.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_react-dom_client_js-node_modules_aws-amplify_ui-react_dist_styles_css.b43d4249e4d3dac9ad7b.js.map +1 -0
  29. mito_ai-0.1.41.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_semver_index_js.9795f79265ddb416864b.js → mito_ai-0.1.42.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_semver_index_js.3f6754ac5116d47de76b.js +2 -240
  30. mito_ai-0.1.42.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_semver_index_js.3f6754ac5116d47de76b.js.map +1 -0
  31. {mito_ai-0.1.41.dist-info → mito_ai-0.1.42.dist-info}/METADATA +1 -1
  32. {mito_ai-0.1.41.dist-info → mito_ai-0.1.42.dist-info}/RECORD +44 -40
  33. mito_ai-0.1.41.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.01a962c68c8fae380f30.js.map +0 -1
  34. mito_ai-0.1.41.data/data/share/jupyter/labextensions/mito_ai/static/node_modules_aws-amplify_core_dist_esm_singleton_apis_fetchAuthSession_mjs.182232e7bc6311fe4528.js +0 -63
  35. mito_ai-0.1.41.data/data/share/jupyter/labextensions/mito_ai/static/node_modules_aws-amplify_core_dist_esm_singleton_apis_fetchAuthSession_mjs.182232e7bc6311fe4528.js.map +0 -1
  36. mito_ai-0.1.41.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.9a70f033717ba8689564.js.map +0 -1
  37. mito_ai-0.1.41.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_auth_dist_esm_providers_cognito_tokenProvider_tokenProvider_mjs.16430abf3466c3153f59.js.map +0 -1
  38. mito_ai-0.1.41.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_core_dist_esm_singleton_Amplify_mjs.3c0035b95fe369aede82.js +0 -2345
  39. mito_ai-0.1.41.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_core_dist_esm_singleton_Amplify_mjs.3c0035b95fe369aede82.js.map +0 -1
  40. mito_ai-0.1.41.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_core_dist_esm_singleton_apis_fetchAuthSession_mjs-node_modul-758875.dc495fd682071d97070c.js.map +0 -1
  41. mito_ai-0.1.41.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_ui-react_dist_esm_index_mjs.61289bff0db44828605b.js.map +0 -1
  42. mito_ai-0.1.41.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_semver_index_js.9795f79265ddb416864b.js.map +0 -1
  43. {mito_ai-0.1.41.data → mito_ai-0.1.42.data}/data/etc/jupyter/jupyter_server_config.d/mito_ai.json +0 -0
  44. {mito_ai-0.1.41.data → mito_ai-0.1.42.data}/data/share/jupyter/labextensions/mito_ai/schemas/mito_ai/toolbar-buttons.json +0 -0
  45. {mito_ai-0.1.41.data → mito_ai-0.1.42.data}/data/share/jupyter/labextensions/mito_ai/static/style.js +0 -0
  46. {mito_ai-0.1.41.data → mito_ai-0.1.42.data}/data/share/jupyter/labextensions/mito_ai/static/style_index_js.5876024bb17dbd6a3ee6.js +0 -0
  47. {mito_ai-0.1.41.data → mito_ai-0.1.42.data}/data/share/jupyter/labextensions/mito_ai/static/style_index_js.5876024bb17dbd6a3ee6.js.map +0 -0
  48. {mito_ai-0.1.41.data → mito_ai-0.1.42.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_dist_esm_index_mjs.6bac1a8c4cc93f15f6b7.js +0 -0
  49. {mito_ai-0.1.41.data → mito_ai-0.1.42.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_dist_esm_index_mjs.6bac1a8c4cc93f15f6b7.js.map +0 -0
  50. {mito_ai-0.1.41.data → mito_ai-0.1.42.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_vscode-diff_dist_index_js.ea55f1f9346638aafbcf.js +0 -0
  51. {mito_ai-0.1.41.data → mito_ai-0.1.42.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_vscode-diff_dist_index_js.ea55f1f9346638aafbcf.js.map +0 -0
  52. {mito_ai-0.1.41.dist-info → mito_ai-0.1.42.dist-info}/WHEEL +0 -0
  53. {mito_ai-0.1.41.dist-info → mito_ai-0.1.42.dist-info}/entry_points.txt +0 -0
  54. {mito_ai-0.1.41.dist-info → mito_ai-0.1.42.dist-info}/licenses/LICENSE +0 -0
mito_ai/__init__.py CHANGED
@@ -14,6 +14,7 @@ from mito_ai.settings.urls import get_settings_urls
14
14
  from mito_ai.rules.urls import get_rules_urls
15
15
  from mito_ai.auth.urls import get_auth_urls
16
16
  from mito_ai.streamlit_preview.urls import get_streamlit_preview_urls
17
+ from mito_ai.app_manager.handlers import AppManagerHandler
17
18
  from mito_ai.file_uploads.urls import get_file_uploads_urls
18
19
 
19
20
  # Force Matplotlib to use the Jupyter inline backend.
@@ -24,6 +25,7 @@ from mito_ai.file_uploads.urls import get_file_uploads_urls
24
25
  # We preempt this by selecting the canonical Jupyter inline backend BEFORE any
25
26
  # Matplotlib import, so figures render inline reliably. This must run very early.
26
27
  # See: https://github.com/streamlit/streamlit/issues/9640
28
+
27
29
  import os
28
30
  os.environ["MPLBACKEND"] = "module://matplotlib_inline.backend_inline"
29
31
 
@@ -82,6 +84,11 @@ def _load_jupyter_server_extension(server_app) -> None: # type: ignore
82
84
  url_path_join(base_url, "mito-ai", "version-check"),
83
85
  VersionCheckHandler,
84
86
  {},
87
+ ),
88
+ (
89
+ url_path_join(base_url, "mito-ai", "app-manager"),
90
+ AppManagerHandler,
91
+ {}
85
92
  )
86
93
  ]
87
94
 
mito_ai/_version.py CHANGED
@@ -1,4 +1,4 @@
1
1
  # This file is auto-generated by Hatchling. As such, do not:
2
2
  # - modify
3
3
  # - track in version control e.g. be sure to add to .gitignore
4
- __version__ = VERSION = '0.1.41'
4
+ __version__ = VERSION = '0.1.42'
@@ -0,0 +1,4 @@
1
+ # Copyright (c) Saga Inc.
2
+ # Distributed under the terms of the GNU Affero General Public License v3.0 License.
3
+
4
+ """App manager module for Mito AI."""
@@ -0,0 +1,129 @@
1
+ # Copyright (c) Saga Inc.
2
+ # Distributed under the terms of the GNU Affero General Public License v3.0 License.
3
+
4
+ # app_manager/handlers.py
5
+ import os
6
+ import time
7
+ import logging
8
+ from typing import Union
9
+ from mito_ai.utils.websocket_base import BaseWebSocketHandler
10
+ from mito_ai.app_manager.models import (
11
+ App,
12
+ AppManagerError,
13
+ ManageAppRequest,
14
+ ManageAppReply,
15
+ ErrorMessage,
16
+ MessageType
17
+ )
18
+ from mito_ai.constants import ACTIVE_STREAMLIT_BASE_URL
19
+ from mito_ai.logger import get_logger
20
+ import requests
21
+
22
+
23
+ class AppManagerHandler(BaseWebSocketHandler):
24
+ """Handler for app management requests."""
25
+
26
+ def initialize(self) -> None:
27
+ """Initialize the WebSocket handler."""
28
+ super().initialize()
29
+ self.log.debug("Initializing app manager websocket connection %s", self.request.path)
30
+
31
+ @property
32
+ def log(self) -> logging.Logger:
33
+ """Use Mito AI logger."""
34
+ return get_logger()
35
+
36
+ async def on_message(self, message: Union[str, bytes]) -> None:
37
+ """Handle incoming messages on the WebSocket."""
38
+ start = time.time()
39
+
40
+ # Convert bytes to string if needed
41
+ if isinstance(message, bytes):
42
+ message = message.decode('utf-8')
43
+
44
+ self.log.debug("App manager message received: %s", message)
45
+
46
+ try:
47
+ # Ensure message is a string before parsing
48
+ if not isinstance(message, str):
49
+ raise ValueError("Message must be a string")
50
+
51
+ parsed_message = self.parse_message(message)
52
+ message_type = parsed_message.get('type')
53
+ message_id = parsed_message.get('message_id')
54
+
55
+ if message_type == MessageType.MANAGE_APP.value:
56
+ # Handle manage app request
57
+ manage_app_request = ManageAppRequest(**parsed_message)
58
+ await self._handle_manage_app(manage_app_request)
59
+ else:
60
+ self.log.error(f"Unknown message type: {message_type}")
61
+ error_response = ErrorMessage(
62
+ error_type="InvalidRequest",
63
+ title=f"Unknown message type: {message_type}",
64
+ message_id=message_id
65
+ )
66
+ self.reply(error_response)
67
+
68
+ except ValueError as e:
69
+ self.log.error("Invalid app manager request", exc_info=e)
70
+ error_response = ErrorMessage(
71
+ error_type=type(e).__name__,
72
+ title=str(e),
73
+ message_id=parsed_message.get('message_id') if 'parsed_message' in locals() else None
74
+ )
75
+ self.reply(error_response)
76
+ except Exception as e:
77
+ self.log.error("Error handling app manager message", exc_info=e)
78
+ error_response = ErrorMessage(
79
+ error_type=type(e).__name__,
80
+ title=str(e),
81
+ message_id=parsed_message.get('message_id') if 'parsed_message' in locals() else None
82
+ )
83
+ self.reply(error_response)
84
+
85
+ latency_ms = round((time.time() - start) * 1000)
86
+ self.log.info(f"App manager handler processed in {latency_ms} ms.")
87
+
88
+ async def _handle_manage_app(self, request: ManageAppRequest) -> None:
89
+ """Handle a manage app request with hardcoded data."""
90
+ try:
91
+ jwt_token = request.jwt_token
92
+ headers = {}
93
+ if jwt_token and jwt_token != 'placeholder-jwt-token':
94
+ headers['Authorization'] = f'Bearer {jwt_token}'
95
+ else:
96
+ self.log.warning("No JWT token provided for API request")
97
+ return
98
+
99
+ manage_apps_response = requests.get(f"{ACTIVE_STREAMLIT_BASE_URL}/manage-apps",
100
+ headers=headers)
101
+ manage_apps_response.raise_for_status()
102
+
103
+ apps_data = manage_apps_response.json()
104
+
105
+ # Create successful response
106
+ reply = ManageAppReply(
107
+ apps=apps_data,
108
+ message_id=request.message_id
109
+ )
110
+ self.reply(reply)
111
+
112
+ except Exception as e:
113
+ self.log.error(f"Error handling manage app request: {e}", exc_info=e)
114
+
115
+ try:
116
+ error = AppManagerError.from_exception(e)
117
+ except Exception:
118
+ error = AppManagerError(
119
+ error_type=type(e).__name__,
120
+ title=str(e)
121
+ )
122
+
123
+ # Return error response
124
+ error_reply = ManageAppReply(
125
+ apps=[],
126
+ error=error,
127
+ message_id=request.message_id
128
+ )
129
+ self.reply(error_reply)
@@ -0,0 +1,58 @@
1
+ # Copyright (c) Saga Inc.
2
+
3
+ # Distributed under the terms of the GNU Affero General Public License v3.0 License.
4
+
5
+ from dataclasses import dataclass, field
6
+ from enum import Enum
7
+ from typing import List, Optional
8
+
9
+ class MessageType(str, Enum):
10
+ """Types of app manager messages."""
11
+ MANAGE_APP = "manage-app"
12
+
13
+
14
+ @dataclass(frozen=True)
15
+ class ManageAppRequest:
16
+ """Request to manage apps."""
17
+ type: str = "manage-app"
18
+ jwt_token: Optional[str] = None
19
+ message_id: Optional[str] = None
20
+
21
+ @dataclass(frozen=True)
22
+ class App:
23
+ """App information."""
24
+ app_name: str
25
+ url: str
26
+ status: str
27
+ created_at: str
28
+
29
+ @dataclass(frozen=True)
30
+ class AppManagerError:
31
+ """Error information for app manager operations."""
32
+ error_type: str
33
+ title: str
34
+ traceback: Optional[str] = None
35
+
36
+ @classmethod
37
+ def from_exception(cls, exc: Exception) -> 'AppManagerError':
38
+ return cls(
39
+ error_type=type(exc).__name__,
40
+ title=str(exc),
41
+ traceback=str(exc)
42
+ )
43
+
44
+ @dataclass(frozen=True)
45
+ class ManageAppReply:
46
+ """Reply to a manage app request."""
47
+ type: str = "manage-app"
48
+ apps: List[App] = field(default_factory=list)
49
+ error: Optional[AppManagerError] = None
50
+ message_id: Optional[str] = None
51
+
52
+ @dataclass(frozen=True)
53
+ class ErrorMessage:
54
+ """Error message."""
55
+ error_type: str
56
+ title: str
57
+ traceback: Optional[str] = None
58
+ message_id: Optional[str] = None
@@ -38,7 +38,7 @@ class AgentExecutionHandler(CompletionHandler[AgentExecutionMetadata]):
38
38
  display_prompt = metadata.input
39
39
 
40
40
  # Add the prompt to the message history
41
- new_ai_optimized_message = create_ai_optimized_message(prompt, metadata.base64EncodedActiveCellOutput)
41
+ new_ai_optimized_message = create_ai_optimized_message(prompt, metadata.base64EncodedActiveCellOutput, metadata.base64EncodedUploadedImage)
42
42
  new_display_optimized_message: ChatCompletionMessageParam = {"role": "user", "content": display_prompt}
43
43
 
44
44
  await message_history.append_message(new_ai_optimized_message, new_display_optimized_message, model, provider, metadata.threadId)
@@ -47,7 +47,7 @@ class ChatCompletionHandler(CompletionHandler[ChatMessageMetadata]):
47
47
  display_prompt = f"```python{metadata.activeCellCode or ''}```{metadata.input}"
48
48
 
49
49
  # Add the prompt to the message history
50
- new_ai_optimized_message = create_ai_optimized_message(prompt, metadata.base64EncodedActiveCellOutput)
50
+ new_ai_optimized_message = create_ai_optimized_message(prompt, metadata.base64EncodedActiveCellOutput, metadata.base64EncodedUploadedImage)
51
51
  new_display_optimized_message: ChatCompletionMessageParam = {"role": "user", "content": display_prompt}
52
52
  await message_history.append_message(new_ai_optimized_message, new_display_optimized_message, model, provider, metadata.threadId)
53
53
 
@@ -110,7 +110,7 @@ class ChatCompletionHandler(CompletionHandler[ChatMessageMetadata]):
110
110
  display_prompt = f"```python{metadata.activeCellCode or ''}```{metadata.input}"
111
111
 
112
112
  # Add the prompt to the message history
113
- new_ai_optimized_message = create_ai_optimized_message(prompt, metadata.base64EncodedActiveCellOutput)
113
+ new_ai_optimized_message = create_ai_optimized_message(prompt, metadata.base64EncodedActiveCellOutput, metadata.base64EncodedUploadedImage)
114
114
  new_display_optimized_message: ChatCompletionMessageParam = {"role": "user", "content": display_prompt}
115
115
  await message_history.append_message(new_ai_optimized_message, new_display_optimized_message, model, provider, metadata.threadId)
116
116
 
@@ -6,25 +6,33 @@ from mito_ai.completions.message_history import GlobalMessageHistory
6
6
  from mito_ai.completions.models import ThreadID
7
7
  from mito_ai.completions.providers import OpenAIProvider
8
8
  from openai.types.chat import ChatCompletionMessageParam
9
- from mito_ai.completions.prompt_builders.chat_system_message import create_chat_system_message_prompt
10
- from mito_ai.completions.prompt_builders.agent_system_message import create_agent_system_message_prompt
9
+ from mito_ai.completions.prompt_builders.chat_system_message import (
10
+ create_chat_system_message_prompt,
11
+ )
12
+ from mito_ai.completions.prompt_builders.agent_system_message import (
13
+ create_agent_system_message_prompt,
14
+ )
15
+
11
16
 
12
17
  async def append_chat_system_message(
13
- message_history: GlobalMessageHistory,
14
- model: str,
15
- provider: OpenAIProvider,
16
- thread_id: ThreadID
18
+ message_history: GlobalMessageHistory,
19
+ model: str,
20
+ provider: OpenAIProvider,
21
+ thread_id: ThreadID,
17
22
  ) -> None:
18
-
23
+
19
24
  # If the system message already exists, do nothing
20
- if any(msg["role"] == "system" for msg in message_history.get_ai_optimized_history(thread_id)):
25
+ if any(
26
+ msg["role"] == "system"
27
+ for msg in message_history.get_ai_optimized_history(thread_id)
28
+ ):
21
29
  return
22
-
30
+
23
31
  system_message_prompt = create_chat_system_message_prompt()
24
32
 
25
33
  system_message: ChatCompletionMessageParam = {
26
34
  "role": "system",
27
- "content": system_message_prompt
35
+ "content": system_message_prompt,
28
36
  }
29
37
 
30
38
  await message_history.append_message(
@@ -32,54 +40,86 @@ async def append_chat_system_message(
32
40
  display_message=system_message,
33
41
  model=model,
34
42
  llm_provider=provider,
35
- thread_id=thread_id
43
+ thread_id=thread_id,
36
44
  )
37
45
 
46
+
38
47
  async def append_agent_system_message(
39
- message_history: GlobalMessageHistory,
40
- model: str,
41
- provider: OpenAIProvider,
42
- thread_id: ThreadID,
43
- isChromeBrowser: bool
48
+ message_history: GlobalMessageHistory,
49
+ model: str,
50
+ provider: OpenAIProvider,
51
+ thread_id: ThreadID,
52
+ isChromeBrowser: bool,
44
53
  ) -> None:
45
-
54
+
46
55
  # If the system message already exists, do nothing
47
- if any(msg["role"] == "system" for msg in message_history.get_ai_optimized_history(thread_id)):
56
+ if any(
57
+ msg["role"] == "system"
58
+ for msg in message_history.get_ai_optimized_history(thread_id)
59
+ ):
48
60
  return
49
-
61
+
50
62
  system_message_prompt = create_agent_system_message_prompt(isChromeBrowser)
51
-
63
+
52
64
  system_message: ChatCompletionMessageParam = {
53
65
  "role": "system",
54
- "content": system_message_prompt
66
+ "content": system_message_prompt,
55
67
  }
56
-
68
+
57
69
  await message_history.append_message(
58
70
  ai_optimized_message=system_message,
59
71
  display_message=system_message,
60
72
  model=model,
61
73
  llm_provider=provider,
62
- thread_id=thread_id
74
+ thread_id=thread_id,
63
75
  )
64
-
65
- def create_ai_optimized_message(text: str, base64EncodedActiveCellOutput: Optional[str] = None) -> ChatCompletionMessageParam:
76
+
77
+
78
+ def create_ai_optimized_message(
79
+ text: str,
80
+ base64EncodedActiveCellOutput: Optional[str] = None,
81
+ base64EncodedUploadedImage: Optional[str] = None,
82
+ ) -> ChatCompletionMessageParam:
66
83
 
67
84
  message_content: Union[str, List[Dict[str, Any]]]
68
- if base64EncodedActiveCellOutput is not None and base64EncodedActiveCellOutput != '':
69
- message_content = [
85
+ has_uploaded_image = (
86
+ base64EncodedUploadedImage is not None and base64EncodedUploadedImage != ""
87
+ )
88
+ has_active_cell_output = (
89
+ base64EncodedActiveCellOutput is not None
90
+ and base64EncodedActiveCellOutput != ""
91
+ )
92
+
93
+ if has_uploaded_image or has_active_cell_output:
94
+ message_content = [
70
95
  {
71
96
  "type": "text",
72
97
  "text": text,
73
- },
74
- {
75
- "type": "image_url",
76
- "image_url": {"url": f"data:image/png;base64,{base64EncodedActiveCellOutput}"},
77
98
  }
78
- ]
99
+ ]
100
+
101
+ if has_uploaded_image:
102
+ message_content.append(
103
+ {
104
+ "type": "image_url",
105
+ "image_url": {
106
+ "url": f"data:image/png;base64,{base64EncodedUploadedImage}"
107
+ },
108
+ }
109
+ )
110
+
111
+ if has_active_cell_output:
112
+ message_content.append(
113
+ {
114
+ "type": "image_url",
115
+ "image_url": {
116
+ "url": f"data:image/png;base64,{base64EncodedActiveCellOutput}"
117
+ },
118
+ }
119
+ )
79
120
  else:
80
121
  message_content = text
81
-
82
- return cast(ChatCompletionMessageParam, {
83
- "role": "user",
84
- "content": message_content
85
- })
122
+
123
+ return cast(
124
+ ChatCompletionMessageParam, {"role": "user", "content": message_content}
125
+ )
@@ -83,6 +83,7 @@ class ChatMessageMetadata():
83
83
  variables: Optional[List[str]] = None
84
84
  files: Optional[List[str]] = None
85
85
  base64EncodedActiveCellOutput: Optional[str] = None
86
+ base64EncodedUploadedImage: Optional[str] = None
86
87
  index: Optional[int] = None
87
88
  stream: bool = False
88
89
  additionalContext: Optional[List[Dict[str, str]]] = None
@@ -96,6 +97,7 @@ class AgentExecutionMetadata():
96
97
  aiOptimizedCells: List[AIOptimizedCell]
97
98
  isChromeBrowser: bool
98
99
  base64EncodedActiveCellOutput: Optional[str] = None
100
+ base64EncodedUploadedImage: Optional[str] = None
99
101
  variables: Optional[List[str]] = None
100
102
  files: Optional[List[str]] = None
101
103
  index: Optional[int] = None
@@ -38,6 +38,7 @@ def get_selected_context_str(additional_context: Optional[List[Dict[str, str]]])
38
38
  selected_variables = [context["value"] for context in additional_context if context.get("type") == "variable"]
39
39
  selected_files = [context["value"] for context in additional_context if context.get("type") == "file"]
40
40
  selected_db_connections = [context["value"] for context in additional_context if context.get("type") == "db"]
41
+ selected_images = [context["value"] for context in additional_context if context.get("type") == "img"]
41
42
 
42
43
  # STEP 2: Create a list of strings (instructions) for each context type
43
44
  context_parts = []
@@ -60,6 +61,12 @@ def get_selected_context_str(additional_context: Optional[List[Dict[str, str]]])
60
61
  + "\n".join(selected_db_connections)
61
62
  )
62
63
 
64
+ if len(selected_images) > 0:
65
+ context_parts.append(
66
+ "The following images have been selected by the user to be used in the task:\n"
67
+ + "\n".join(selected_images)
68
+ )
69
+
63
70
  # STEP 3: Combine into a single string
64
71
 
65
72
  return "\n\n".join(context_parts)
@@ -0,0 +1,51 @@
1
+ # Copyright (c) Saga Inc.
2
+ # Distributed under the terms of the GNU Affero General Public License v3.0 License.
3
+
4
+ from mito_ai.completions.completion_handlers.utils import create_ai_optimized_message
5
+
6
+
7
+ def test_text_only_message():
8
+ """Test scenario where the user only inputs text"""
9
+ result = create_ai_optimized_message("Hello world")
10
+
11
+ assert result["role"] == "user"
12
+ assert result["content"] == "Hello world"
13
+
14
+
15
+ def test_message_with_uploaded_image():
16
+ """Test scenario where the user uploads an image"""
17
+ result = create_ai_optimized_message(
18
+ text="Analyze this", base64EncodedUploadedImage="image_data"
19
+ )
20
+
21
+ assert result["role"] == "user"
22
+ assert isinstance(result["content"], list)
23
+ assert result["content"][0]["type"] == "text"
24
+ assert result["content"][1]["type"] == "image_url"
25
+
26
+
27
+ def test_message_with_active_cell_output():
28
+ """Test scenario where the active cell has an output"""
29
+ result = create_ai_optimized_message(
30
+ text="Analyze this", base64EncodedActiveCellOutput="cell_output_data"
31
+ )
32
+
33
+ assert result["role"] == "user"
34
+ assert isinstance(result["content"], list)
35
+ assert result["content"][0]["type"] == "text"
36
+ assert result["content"][1]["type"] == "image_url"
37
+
38
+
39
+ def test_message_with_uploaded_image_and_active_cell_output():
40
+ """Test scenario where the user uploads an image and the active cell has an output"""
41
+ result = create_ai_optimized_message(
42
+ text="Analyze this",
43
+ base64EncodedUploadedImage="image_data",
44
+ base64EncodedActiveCellOutput="cell_output_data",
45
+ )
46
+
47
+ assert result["role"] == "user"
48
+ assert isinstance(result["content"], list)
49
+ assert result["content"][0]["type"] == "text"
50
+ assert result["content"][1]["type"] == "image_url"
51
+ assert result["content"][2]["type"] == "image_url"