agent-starter-pack 0.5.2__py3-none-any.whl → 0.6.0__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.
- {agent_starter_pack-0.5.2.dist-info → agent_starter_pack-0.6.0.dist-info}/METADATA +2 -2
- {agent_starter_pack-0.5.2.dist-info → agent_starter_pack-0.6.0.dist-info}/RECORD +66 -40
- agents/adk_base/notebooks/adk_app_testing.ipynb +1 -1
- agents/adk_base/template/.templateconfig.yaml +1 -1
- agents/adk_gemini_fullstack/README.md +148 -0
- agents/adk_gemini_fullstack/app/agent.py +349 -0
- agents/adk_gemini_fullstack/app/config.py +11 -0
- agents/adk_gemini_fullstack/notebooks/adk_app_testing.ipynb +353 -0
- agents/adk_gemini_fullstack/notebooks/evaluating_adk_agent.ipynb +1528 -0
- agents/adk_gemini_fullstack/template/.templateconfig.yaml +37 -0
- agents/adk_gemini_fullstack/tests/integration/test_agent.py +58 -0
- agents/agentic_rag/notebooks/adk_app_testing.ipynb +1 -1
- agents/agentic_rag/template/.templateconfig.yaml +1 -1
- agents/crewai_coding_crew/template/.templateconfig.yaml +1 -1
- agents/langgraph_base_react/template/.templateconfig.yaml +1 -1
- src/base_template/Makefile +21 -2
- src/base_template/README.md +8 -3
- src/base_template/pyproject.toml +1 -4
- src/cli/commands/create.py +17 -10
- src/cli/utils/template.py +13 -10
- src/data_ingestion/data_ingestion_pipeline/components/process_data.py +1 -1
- src/deployment_targets/agent_engine/app/agent_engine_app.py +17 -5
- src/deployment_targets/cloud_run/app/server.py +17 -2
- src/deployment_targets/cloud_run/tests/integration/test_server_e2e.py +1 -1
- src/deployment_targets/cloud_run/tests/load_test/.results/.placeholder +321 -0
- src/frontends/adk_gemini_fullstack/frontend/components.json +21 -0
- src/frontends/adk_gemini_fullstack/frontend/eslint.config.js +28 -0
- src/frontends/adk_gemini_fullstack/frontend/index.html +12 -0
- src/frontends/adk_gemini_fullstack/frontend/package-lock.json +5829 -0
- src/frontends/adk_gemini_fullstack/frontend/package.json +46 -0
- src/frontends/adk_gemini_fullstack/frontend/public/vite.svg +1 -0
- src/frontends/adk_gemini_fullstack/frontend/src/App.tsx +565 -0
- src/frontends/adk_gemini_fullstack/frontend/src/components/ActivityTimeline.tsx +244 -0
- src/frontends/adk_gemini_fullstack/frontend/src/components/ChatMessagesView.tsx +419 -0
- src/frontends/adk_gemini_fullstack/frontend/src/components/InputForm.tsx +60 -0
- src/frontends/adk_gemini_fullstack/frontend/src/components/WelcomeScreen.tsx +56 -0
- src/frontends/adk_gemini_fullstack/frontend/src/components/ui/badge.tsx +46 -0
- src/frontends/adk_gemini_fullstack/frontend/src/components/ui/button.tsx +59 -0
- src/frontends/adk_gemini_fullstack/frontend/src/components/ui/card.tsx +92 -0
- src/frontends/adk_gemini_fullstack/frontend/src/components/ui/input.tsx +21 -0
- src/frontends/adk_gemini_fullstack/frontend/src/components/ui/scroll-area.tsx +56 -0
- src/frontends/adk_gemini_fullstack/frontend/src/components/ui/select.tsx +183 -0
- src/frontends/adk_gemini_fullstack/frontend/src/components/ui/tabs.tsx +64 -0
- src/frontends/adk_gemini_fullstack/frontend/src/components/ui/textarea.tsx +18 -0
- src/frontends/adk_gemini_fullstack/frontend/src/global.css +154 -0
- src/frontends/adk_gemini_fullstack/frontend/src/main.tsx +13 -0
- src/frontends/adk_gemini_fullstack/frontend/src/utils.ts +7 -0
- src/frontends/adk_gemini_fullstack/frontend/src/vite-env.d.ts +1 -0
- src/frontends/adk_gemini_fullstack/frontend/tsconfig.json +28 -0
- src/frontends/adk_gemini_fullstack/frontend/tsconfig.node.json +24 -0
- src/frontends/adk_gemini_fullstack/frontend/vite.config.ts +26 -0
- src/resources/locks/uv-adk_base-agent_engine.lock +303 -251
- src/resources/locks/uv-adk_base-cloud_run.lock +367 -306
- src/resources/locks/uv-adk_gemini_fullstack-agent_engine.lock +3217 -0
- src/resources/locks/uv-adk_gemini_fullstack-cloud_run.lock +3513 -0
- src/resources/locks/uv-agentic_rag-agent_engine.lock +621 -565
- src/resources/locks/uv-agentic_rag-cloud_run.lock +854 -782
- src/resources/locks/uv-crewai_coding_crew-agent_engine.lock +860 -723
- src/resources/locks/uv-crewai_coding_crew-cloud_run.lock +1139 -960
- src/resources/locks/uv-langgraph_base_react-agent_engine.lock +718 -587
- src/resources/locks/uv-langgraph_base_react-cloud_run.lock +977 -815
- src/resources/locks/uv-live_api-cloud_run.lock +784 -709
- src/frontends/streamlit_adk/frontend/side_bar.py +0 -214
- src/frontends/streamlit_adk/frontend/streamlit_app.py +0 -314
- src/frontends/streamlit_adk/frontend/style/app_markdown.py +0 -37
- src/frontends/streamlit_adk/frontend/utils/chat_utils.py +0 -84
- src/frontends/streamlit_adk/frontend/utils/local_chat_history.py +0 -110
- src/frontends/streamlit_adk/frontend/utils/message_editing.py +0 -61
- src/frontends/streamlit_adk/frontend/utils/multimodal_utils.py +0 -223
- src/frontends/streamlit_adk/frontend/utils/stream_handler.py +0 -311
- src/frontends/streamlit_adk/frontend/utils/title_summary.py +0 -129
- {agent_starter_pack-0.5.2.dist-info → agent_starter_pack-0.6.0.dist-info}/WHEEL +0 -0
- {agent_starter_pack-0.5.2.dist-info → agent_starter_pack-0.6.0.dist-info}/entry_points.txt +0 -0
- {agent_starter_pack-0.5.2.dist-info → agent_starter_pack-0.6.0.dist-info}/licenses/LICENSE +0 -0
- /src/{deployment_targets/agent_engine → base_template}/app/utils/gcs.py +0 -0
@@ -1,110 +0,0 @@
|
|
1
|
-
# Copyright 2025 Google LLC
|
2
|
-
#
|
3
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
-
# you may not use this file except in compliance with the License.
|
5
|
-
# You may obtain a copy of the License at
|
6
|
-
#
|
7
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
-
#
|
9
|
-
# Unless required by applicable law or agreed to in writing, software
|
10
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
-
# See the License for the specific language governing permissions and
|
13
|
-
# limitations under the License.
|
14
|
-
|
15
|
-
import os
|
16
|
-
from datetime import datetime
|
17
|
-
|
18
|
-
import yaml
|
19
|
-
|
20
|
-
from frontend.utils.title_summary import DummySummarizer, TitleGenerator
|
21
|
-
|
22
|
-
|
23
|
-
class LocalChatMessageHistory:
|
24
|
-
"""Manages local storage and retrieval of chat message history."""
|
25
|
-
|
26
|
-
def __init__(
|
27
|
-
self,
|
28
|
-
user_id: str,
|
29
|
-
session_id: str = "default",
|
30
|
-
base_dir: str = ".streamlit_chats",
|
31
|
-
) -> None:
|
32
|
-
self.user_id = user_id
|
33
|
-
self.session_id = session_id
|
34
|
-
self.base_dir = base_dir
|
35
|
-
self.user_dir = os.path.join(self.base_dir, self.user_id)
|
36
|
-
self.session_file = os.path.join(self.user_dir, f"{session_id}.yaml")
|
37
|
-
try:
|
38
|
-
self.title_generator = TitleGenerator()
|
39
|
-
except: # noqa: E722
|
40
|
-
self.title_generator: TitleGenerator = DummySummarizer() # type: ignore
|
41
|
-
os.makedirs(self.user_dir, exist_ok=True)
|
42
|
-
|
43
|
-
def get_session(self, session_id: str) -> None:
|
44
|
-
"""Updates the session ID and file path for the current session."""
|
45
|
-
self.session_id = session_id
|
46
|
-
self.session_file = os.path.join(self.user_dir, f"{session_id}.yaml")
|
47
|
-
|
48
|
-
def get_all_conversations(self) -> dict[str, dict]:
|
49
|
-
"""Retrieves all conversations for the current user."""
|
50
|
-
conversations = {}
|
51
|
-
for filename in os.listdir(self.user_dir):
|
52
|
-
if filename.endswith(".yaml"):
|
53
|
-
file_path = os.path.join(self.user_dir, filename)
|
54
|
-
with open(file_path) as f:
|
55
|
-
conversation = yaml.safe_load(f)
|
56
|
-
if not isinstance(conversation, list) or len(conversation) > 1:
|
57
|
-
raise ValueError(
|
58
|
-
f"""Invalid format in {file_path}.
|
59
|
-
YAML file can only contain one conversation with the following
|
60
|
-
structure.
|
61
|
-
- messages:
|
62
|
-
- content: [message text]
|
63
|
-
- type: (human or ai)"""
|
64
|
-
)
|
65
|
-
conversation = conversation[0]
|
66
|
-
if "title" not in conversation:
|
67
|
-
conversation["title"] = filename
|
68
|
-
conversations[filename[:-5]] = conversation
|
69
|
-
return dict(
|
70
|
-
sorted(conversations.items(), key=lambda x: x[1].get("update_time", ""))
|
71
|
-
)
|
72
|
-
|
73
|
-
def upsert_session(self, session: dict) -> None:
|
74
|
-
"""Updates or inserts a session into the local storage."""
|
75
|
-
session["update_time"] = datetime.now().isoformat()
|
76
|
-
with open(self.session_file, "w") as f:
|
77
|
-
yaml.dump(
|
78
|
-
[session],
|
79
|
-
f,
|
80
|
-
allow_unicode=True,
|
81
|
-
default_flow_style=False,
|
82
|
-
encoding="utf-8",
|
83
|
-
)
|
84
|
-
|
85
|
-
def set_title(self, session: dict) -> None:
|
86
|
-
"""
|
87
|
-
Set the title for the given session.
|
88
|
-
|
89
|
-
This method generates a title for the session based on its messages.
|
90
|
-
If the session has messages, it appends a special message to prompt
|
91
|
-
for title creation, generates the title using a title chain, and
|
92
|
-
updates the session with the new title.
|
93
|
-
|
94
|
-
Args:
|
95
|
-
session (dict): A dictionary containing session information,
|
96
|
-
including messages.
|
97
|
-
|
98
|
-
Returns:
|
99
|
-
None
|
100
|
-
"""
|
101
|
-
if session["messages"]:
|
102
|
-
session["title"] = self.title_generator.summarize(
|
103
|
-
events=session["messages"]
|
104
|
-
)
|
105
|
-
self.upsert_session(session)
|
106
|
-
|
107
|
-
def clear(self) -> None:
|
108
|
-
"""Removes the current session file if it exists."""
|
109
|
-
if os.path.exists(self.session_file):
|
110
|
-
os.remove(self.session_file)
|
@@ -1,61 +0,0 @@
|
|
1
|
-
# Copyright 2025 Google LLC
|
2
|
-
#
|
3
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
-
# you may not use this file except in compliance with the License.
|
5
|
-
# You may obtain a copy of the License at
|
6
|
-
#
|
7
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
-
#
|
9
|
-
# Unless required by applicable law or agreed to in writing, software
|
10
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
-
# See the License for the specific language governing permissions and
|
13
|
-
# limitations under the License.
|
14
|
-
|
15
|
-
# fmt: off
|
16
|
-
|
17
|
-
from typing import Any
|
18
|
-
|
19
|
-
from google.adk.events.event import Event
|
20
|
-
|
21
|
-
|
22
|
-
class MessageEditing:
|
23
|
-
"""Provides methods for editing, refreshing, and deleting chat messages."""
|
24
|
-
|
25
|
-
@staticmethod
|
26
|
-
def edit_message(st: Any, button_idx: int, message_type: str) -> None:
|
27
|
-
"""Edit a message in the chat history."""
|
28
|
-
button_id = f"edit_box_{button_idx}"
|
29
|
-
# Handle Event type messages
|
30
|
-
message = st.session_state.user_chats[st.session_state["session_id"]]["messages"][button_idx]
|
31
|
-
# Convert to Event if it's not already
|
32
|
-
event = message if isinstance(message, Event) else Event.model_validate(message)
|
33
|
-
# Update the text content in the event
|
34
|
-
if hasattr(event.content, 'parts'):
|
35
|
-
for part in event.content.parts:
|
36
|
-
if hasattr(part, 'text'):
|
37
|
-
part.text = st.session_state[button_id]
|
38
|
-
break
|
39
|
-
# Update the message in the session state
|
40
|
-
st.session_state.user_chats[st.session_state["session_id"]]["messages"][button_idx] = event.model_dump()
|
41
|
-
|
42
|
-
@staticmethod
|
43
|
-
def refresh_message(st: Any, button_idx: int, content: str) -> None:
|
44
|
-
"""Refresh a message in the chat history."""
|
45
|
-
messages = st.session_state.user_chats[st.session_state["session_id"]][
|
46
|
-
"messages"
|
47
|
-
]
|
48
|
-
st.session_state.user_chats[st.session_state["session_id"]][
|
49
|
-
"messages"
|
50
|
-
] = messages[:button_idx]
|
51
|
-
st.session_state.modified_prompt = content
|
52
|
-
|
53
|
-
@staticmethod
|
54
|
-
def delete_message(st: Any, button_idx: int) -> None:
|
55
|
-
"""Delete a message from the chat history."""
|
56
|
-
messages = st.session_state.user_chats[st.session_state["session_id"]][
|
57
|
-
"messages"
|
58
|
-
]
|
59
|
-
st.session_state.user_chats[st.session_state["session_id"]][
|
60
|
-
"messages"
|
61
|
-
] = messages[:button_idx]
|
@@ -1,223 +0,0 @@
|
|
1
|
-
# Copyright 2025 Google LLC
|
2
|
-
#
|
3
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
-
# you may not use this file except in compliance with the License.
|
5
|
-
# You may obtain a copy of the License at
|
6
|
-
#
|
7
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
-
#
|
9
|
-
# Unless required by applicable law or agreed to in writing, software
|
10
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
-
# See the License for the specific language governing permissions and
|
13
|
-
# limitations under the License.
|
14
|
-
|
15
|
-
import base64
|
16
|
-
from typing import Any
|
17
|
-
from urllib.parse import quote
|
18
|
-
|
19
|
-
import google.cloud.storage as storage
|
20
|
-
from google.genai import types
|
21
|
-
|
22
|
-
HELP_MESSAGE_MULTIMODALITY = (
|
23
|
-
"For Gemini models to access the URIs you provide, store them in "
|
24
|
-
"Google Cloud Storage buckets within the same project used by Gemini."
|
25
|
-
)
|
26
|
-
|
27
|
-
HELP_GCS_CHECKBOX = (
|
28
|
-
"Enabling GCS upload will increase the app observability by avoiding"
|
29
|
-
" forwarding and logging large byte strings within the app."
|
30
|
-
)
|
31
|
-
|
32
|
-
|
33
|
-
def format_content(parts: list[types.Part] | str) -> str:
|
34
|
-
"""Formats content as a string, handling both text and multimedia inputs."""
|
35
|
-
# Check if parts is empty
|
36
|
-
if not parts:
|
37
|
-
return ""
|
38
|
-
|
39
|
-
# Handle case where parts might be a string
|
40
|
-
if isinstance(parts, str):
|
41
|
-
return parts
|
42
|
-
|
43
|
-
markdown = ""
|
44
|
-
text = ""
|
45
|
-
for part in parts:
|
46
|
-
# Convert to Part object if it's a dictionary
|
47
|
-
if isinstance(part, dict):
|
48
|
-
part = types.Part.model_validate(part)
|
49
|
-
if part.text:
|
50
|
-
text = part.text
|
51
|
-
# Handle inline data (images and other media)
|
52
|
-
elif part.inline_data:
|
53
|
-
if "image" in part.inline_data.mime_type:
|
54
|
-
# For images, create an HTML img tag
|
55
|
-
image_data = base64.b64encode(part.inline_data.data).decode("utf-8")
|
56
|
-
image_markdown = f'<img src="data:{part.inline_data.mime_type};base64,{image_data}" width="100">'
|
57
|
-
markdown = (
|
58
|
-
markdown
|
59
|
-
+ f"""
|
60
|
-
- {image_markdown}
|
61
|
-
"""
|
62
|
-
)
|
63
|
-
else:
|
64
|
-
# For other media types
|
65
|
-
markdown = markdown + f"- Local media: {part.inline_data.mime_type}\n"
|
66
|
-
# Handle file data (GCS files)
|
67
|
-
elif part.file_data:
|
68
|
-
if part.file_data.mime_type and "image" in part.file_data.mime_type:
|
69
|
-
# GCS images
|
70
|
-
image_url = gs_uri_to_https_url(part.file_data.file_uri)
|
71
|
-
image_markdown = f'<img src="{image_url}" width="100">'
|
72
|
-
markdown = (
|
73
|
-
markdown
|
74
|
-
+ f"""
|
75
|
-
- {image_markdown}
|
76
|
-
"""
|
77
|
-
)
|
78
|
-
else:
|
79
|
-
# GCS other media
|
80
|
-
image_url = gs_uri_to_https_url(part.file_data.file_uri)
|
81
|
-
markdown = (
|
82
|
-
markdown + f"- Remote media: "
|
83
|
-
f"[{part.file_data.file_uri}]({image_url})\n"
|
84
|
-
)
|
85
|
-
markdown = (
|
86
|
-
markdown
|
87
|
-
+ f"""
|
88
|
-
|
89
|
-
{text}"""
|
90
|
-
)
|
91
|
-
return markdown
|
92
|
-
|
93
|
-
|
94
|
-
def get_gcs_blob_mime_type(gcs_uri: str) -> str | None:
|
95
|
-
"""Fetches the MIME type (content type) of a Google Cloud Storage blob.
|
96
|
-
|
97
|
-
Args:
|
98
|
-
gcs_uri (str): The GCS URI of the blob in the format "gs://bucket-name/object-name".
|
99
|
-
|
100
|
-
Returns:
|
101
|
-
str: The MIME type of the blob (e.g., "image/jpeg", "text/plain") if found,
|
102
|
-
or None if the blob does not exist or an error occurs.
|
103
|
-
"""
|
104
|
-
storage_client = storage.Client()
|
105
|
-
|
106
|
-
try:
|
107
|
-
bucket_name, object_name = gcs_uri.replace("gs://", "").split("/", 1)
|
108
|
-
|
109
|
-
bucket = storage_client.bucket(bucket_name)
|
110
|
-
blob = bucket.blob(object_name)
|
111
|
-
blob.reload()
|
112
|
-
return blob.content_type
|
113
|
-
except Exception as e:
|
114
|
-
print(f"Error retrieving MIME type for {gcs_uri}: {e}")
|
115
|
-
return None # Indicate failure
|
116
|
-
|
117
|
-
|
118
|
-
def get_parts_from_files(
|
119
|
-
upload_gcs_checkbox: bool, uploaded_files: list[Any], gcs_uris: str
|
120
|
-
) -> list[types.Part]:
|
121
|
-
"""Processes uploaded files and GCS URIs to create a list of content parts."""
|
122
|
-
parts = []
|
123
|
-
# read from local directly
|
124
|
-
if not upload_gcs_checkbox:
|
125
|
-
for uploaded_file in uploaded_files:
|
126
|
-
im_bytes = uploaded_file.read()
|
127
|
-
if "image" in uploaded_file.type:
|
128
|
-
# Create inline_data Part for images
|
129
|
-
blob = types.Blob(
|
130
|
-
mime_type=uploaded_file.type,
|
131
|
-
data=base64.b64encode(im_bytes).decode("utf-8"),
|
132
|
-
)
|
133
|
-
parts.append(types.Part(inline_data=blob))
|
134
|
-
else:
|
135
|
-
# Create inline_data Part for other file types
|
136
|
-
blob = types.Blob(
|
137
|
-
mime_type=uploaded_file.type,
|
138
|
-
data=base64.b64encode(im_bytes).decode("utf-8"),
|
139
|
-
)
|
140
|
-
parts.append(types.Part(inline_data=blob))
|
141
|
-
|
142
|
-
# Process GCS URIs
|
143
|
-
if gcs_uris != "":
|
144
|
-
for uri in gcs_uris.split(","):
|
145
|
-
mime_type = get_gcs_blob_mime_type(uri)
|
146
|
-
if mime_type:
|
147
|
-
# Create file_data Part for GCS URIs
|
148
|
-
file_data = types.FileData(file_uri=uri, mime_type=mime_type)
|
149
|
-
parts.append(types.Part(file_data=file_data))
|
150
|
-
|
151
|
-
return parts
|
152
|
-
|
153
|
-
|
154
|
-
def upload_bytes_to_gcs(
|
155
|
-
bucket_name: str,
|
156
|
-
blob_name: str,
|
157
|
-
file_bytes: bytes,
|
158
|
-
content_type: str | None = None,
|
159
|
-
) -> str:
|
160
|
-
"""Uploads a bytes object to Google Cloud Storage and returns the GCS URI.
|
161
|
-
|
162
|
-
Args:
|
163
|
-
bucket_name: The name of the GCS bucket.
|
164
|
-
blob_name: The desired name for the uploaded file in GCS.
|
165
|
-
file_bytes: The file's content as a bytes object.
|
166
|
-
content_type (optional): The MIME type of the file (e.g., "image/png").
|
167
|
-
If not provided, GCS will try to infer it.
|
168
|
-
|
169
|
-
Returns:
|
170
|
-
str: The GCS URI (gs://bucket_name/blob_name) of the uploaded file.
|
171
|
-
|
172
|
-
Raises:
|
173
|
-
GoogleCloudError: If there's an issue with the GCS operation.
|
174
|
-
"""
|
175
|
-
storage_client = storage.Client()
|
176
|
-
bucket = storage_client.bucket(bucket_name)
|
177
|
-
blob = bucket.blob(blob_name)
|
178
|
-
blob.upload_from_string(data=file_bytes, content_type=content_type)
|
179
|
-
# Construct and return the GCS URI
|
180
|
-
gcs_uri = f"gs://{bucket_name}/{blob_name}"
|
181
|
-
return gcs_uri
|
182
|
-
|
183
|
-
|
184
|
-
def gs_uri_to_https_url(gs_uri: str) -> str:
|
185
|
-
"""Converts a GS URI to an HTTPS URL without authentication.
|
186
|
-
|
187
|
-
Args:
|
188
|
-
gs_uri: The GS URI in the format gs://<bucket>/<object>.
|
189
|
-
|
190
|
-
Returns:
|
191
|
-
The corresponding HTTPS URL, or None if the GS URI is invalid.
|
192
|
-
"""
|
193
|
-
|
194
|
-
if not gs_uri.startswith("gs://"):
|
195
|
-
raise ValueError("Invalid GS URI format")
|
196
|
-
|
197
|
-
gs_uri = gs_uri[5:]
|
198
|
-
|
199
|
-
# Extract bucket and object names, then URL encode the object name
|
200
|
-
bucket_name, object_name = gs_uri.split("/", 1)
|
201
|
-
object_name = quote(object_name)
|
202
|
-
|
203
|
-
# Construct the HTTPS URL
|
204
|
-
https_url = f"https://storage.mtls.cloud.google.com/{bucket_name}/{object_name}"
|
205
|
-
return https_url
|
206
|
-
|
207
|
-
|
208
|
-
def upload_files_to_gcs(st: Any, bucket_name: str, files_to_upload: list[Any]) -> None:
|
209
|
-
"""Upload multiple files to Google Cloud Storage and store URIs in session state."""
|
210
|
-
bucket_name = bucket_name.replace("gs://", "")
|
211
|
-
uploaded_uris = []
|
212
|
-
for file in files_to_upload:
|
213
|
-
if file:
|
214
|
-
file_bytes = file.read()
|
215
|
-
gcs_uri = upload_bytes_to_gcs(
|
216
|
-
bucket_name=bucket_name,
|
217
|
-
blob_name=file.name,
|
218
|
-
file_bytes=file_bytes,
|
219
|
-
content_type=file.type,
|
220
|
-
)
|
221
|
-
uploaded_uris.append(gcs_uri)
|
222
|
-
st.session_state.uploader_key += 1
|
223
|
-
st.session_state["gcs_uris_to_be_sent"] = ",".join(uploaded_uris)
|