openai-sdk-helpers 0.1.2__py3-none-any.whl → 0.1.4__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.
- openai_sdk_helpers/streamlit_app/app.py +122 -27
- {openai_sdk_helpers-0.1.2.dist-info → openai_sdk_helpers-0.1.4.dist-info}/METADATA +1 -1
- {openai_sdk_helpers-0.1.2.dist-info → openai_sdk_helpers-0.1.4.dist-info}/RECORD +6 -6
- {openai_sdk_helpers-0.1.2.dist-info → openai_sdk_helpers-0.1.4.dist-info}/WHEEL +0 -0
- {openai_sdk_helpers-0.1.2.dist-info → openai_sdk_helpers-0.1.4.dist-info}/entry_points.txt +0 -0
- {openai_sdk_helpers-0.1.2.dist-info → openai_sdk_helpers-0.1.4.dist-info}/licenses/LICENSE +0 -0
|
@@ -8,6 +8,7 @@ rendering, response execution, and resource cleanup.
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
10
|
import json
|
|
11
|
+
import os
|
|
11
12
|
import tempfile
|
|
12
13
|
from pathlib import Path
|
|
13
14
|
from typing import Any
|
|
@@ -30,22 +31,30 @@ from openai_sdk_helpers.utils import (
|
|
|
30
31
|
log,
|
|
31
32
|
)
|
|
32
33
|
|
|
33
|
-
# Supported file extensions for OpenAI Assistants file search
|
|
34
|
+
# Supported file extensions for OpenAI Assistants file search and vision
|
|
34
35
|
SUPPORTED_FILE_EXTENSIONS = (
|
|
35
36
|
".csv",
|
|
36
37
|
".docx",
|
|
38
|
+
".gif",
|
|
37
39
|
".html",
|
|
38
40
|
".json",
|
|
41
|
+
".jpeg",
|
|
42
|
+
".jpg",
|
|
39
43
|
".md",
|
|
40
44
|
".pdf",
|
|
45
|
+
".png",
|
|
41
46
|
".pptx",
|
|
42
47
|
".txt",
|
|
48
|
+
".webp",
|
|
43
49
|
".xlsx",
|
|
44
50
|
)
|
|
45
51
|
|
|
46
52
|
|
|
47
53
|
def _validate_file_type(filename: str) -> bool:
|
|
48
|
-
"""Check if a file has a supported extension for
|
|
54
|
+
"""Check if a file has a supported extension for upload.
|
|
55
|
+
|
|
56
|
+
Supports both document formats (for file search) and image formats
|
|
57
|
+
(for vision analysis).
|
|
49
58
|
|
|
50
59
|
Parameters
|
|
51
60
|
----------
|
|
@@ -61,6 +70,32 @@ def _validate_file_type(filename: str) -> bool:
|
|
|
61
70
|
return file_ext in SUPPORTED_FILE_EXTENSIONS
|
|
62
71
|
|
|
63
72
|
|
|
73
|
+
def _cleanup_temp_files(file_paths: list[str] | None = None) -> None:
|
|
74
|
+
"""Delete temporary files that were created for uploads.
|
|
75
|
+
|
|
76
|
+
Parameters
|
|
77
|
+
----------
|
|
78
|
+
file_paths : list[str] or None, default None
|
|
79
|
+
Specific file paths to delete. If None, deletes all tracked
|
|
80
|
+
temporary files from session state.
|
|
81
|
+
|
|
82
|
+
Notes
|
|
83
|
+
-----
|
|
84
|
+
Silently ignores errors when deleting files that may have already
|
|
85
|
+
been removed or are inaccessible.
|
|
86
|
+
"""
|
|
87
|
+
paths_to_delete = file_paths or st.session_state.get("temp_file_paths", [])
|
|
88
|
+
for path in paths_to_delete:
|
|
89
|
+
try:
|
|
90
|
+
if os.path.exists(path):
|
|
91
|
+
os.remove(path)
|
|
92
|
+
except (OSError, IOError):
|
|
93
|
+
pass # Silently ignore if file already deleted or inaccessible
|
|
94
|
+
|
|
95
|
+
if file_paths is None:
|
|
96
|
+
st.session_state["temp_file_paths"] = []
|
|
97
|
+
|
|
98
|
+
|
|
64
99
|
def _extract_assistant_text(response: BaseResponse[Any]) -> str:
|
|
65
100
|
"""Extract the latest assistant message as readable text.
|
|
66
101
|
|
|
@@ -86,15 +121,33 @@ def _extract_assistant_text(response: BaseResponse[Any]) -> str:
|
|
|
86
121
|
if message is None:
|
|
87
122
|
return ""
|
|
88
123
|
|
|
124
|
+
# Check if the message content has output_text attribute
|
|
125
|
+
output_text = getattr(message.content, "output_text", None)
|
|
126
|
+
if output_text:
|
|
127
|
+
return str(output_text)
|
|
128
|
+
|
|
89
129
|
content = getattr(message.content, "content", None)
|
|
90
130
|
if content is None:
|
|
91
131
|
return ""
|
|
92
132
|
|
|
93
133
|
text_parts: list[str] = []
|
|
94
134
|
for part in ensure_list(content):
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
135
|
+
# Handle both dict-like parts and object-like parts
|
|
136
|
+
text_content = None
|
|
137
|
+
if hasattr(part, "text"):
|
|
138
|
+
text_content = getattr(part, "text", None)
|
|
139
|
+
elif isinstance(part, dict):
|
|
140
|
+
text_content = part.get("text")
|
|
141
|
+
|
|
142
|
+
if text_content:
|
|
143
|
+
# If text_content is a string, use it directly (dict-style)
|
|
144
|
+
if isinstance(text_content, str):
|
|
145
|
+
text_parts.append(text_content)
|
|
146
|
+
# If text_content is an object with a value attribute, extract that value (object-style)
|
|
147
|
+
else:
|
|
148
|
+
text_value = getattr(text_content, "value", None)
|
|
149
|
+
if text_value:
|
|
150
|
+
text_parts.append(text_value)
|
|
98
151
|
if text_parts:
|
|
99
152
|
return "\n\n".join(text_parts)
|
|
100
153
|
return ""
|
|
@@ -223,7 +276,8 @@ def _reset_chat(close_response: bool = True) -> None:
|
|
|
223
276
|
"""Clear conversation and optionally close the response session.
|
|
224
277
|
|
|
225
278
|
Saves the current conversation to disk, closes the response to clean
|
|
226
|
-
up resources, and clears the chat history from session state.
|
|
279
|
+
up resources, and clears the chat history from session state. Also
|
|
280
|
+
cleans up any temporary files that were created for uploads.
|
|
227
281
|
|
|
228
282
|
Parameters
|
|
229
283
|
----------
|
|
@@ -234,13 +288,17 @@ def _reset_chat(close_response: bool = True) -> None:
|
|
|
234
288
|
Notes
|
|
235
289
|
-----
|
|
236
290
|
This function mutates st.session_state in-place, clearing the
|
|
237
|
-
chat_history and
|
|
291
|
+
chat_history, response_instance, and temp_file_paths keys.
|
|
238
292
|
"""
|
|
239
293
|
response = st.session_state.get("response_instance")
|
|
240
294
|
if close_response and isinstance(response, BaseResponse):
|
|
241
295
|
filepath = f"./data/{response.name}.{response.uuid}.json"
|
|
242
296
|
response.save(filepath)
|
|
243
297
|
response.close()
|
|
298
|
+
|
|
299
|
+
# Clean up temporary files
|
|
300
|
+
_cleanup_temp_files()
|
|
301
|
+
|
|
244
302
|
st.session_state["chat_history"] = []
|
|
245
303
|
st.session_state.pop("response_instance", None)
|
|
246
304
|
|
|
@@ -249,7 +307,8 @@ def _init_session_state() -> None:
|
|
|
249
307
|
"""Initialize Streamlit session state for chat functionality.
|
|
250
308
|
|
|
251
309
|
Creates the chat_history list in session state if it doesn't exist,
|
|
252
|
-
enabling conversation persistence across Streamlit reruns.
|
|
310
|
+
enabling conversation persistence across Streamlit reruns. Also
|
|
311
|
+
initializes a list for tracking temporary file paths that need cleanup.
|
|
253
312
|
|
|
254
313
|
Notes
|
|
255
314
|
-----
|
|
@@ -258,8 +317,12 @@ def _init_session_state() -> None:
|
|
|
258
317
|
"""
|
|
259
318
|
if "chat_history" not in st.session_state:
|
|
260
319
|
st.session_state["chat_history"] = []
|
|
261
|
-
if "
|
|
262
|
-
st.session_state["
|
|
320
|
+
if "temp_file_paths" not in st.session_state:
|
|
321
|
+
st.session_state["temp_file_paths"] = []
|
|
322
|
+
if "current_attachments" not in st.session_state:
|
|
323
|
+
st.session_state["current_attachments"] = []
|
|
324
|
+
if "attachment_names" not in st.session_state:
|
|
325
|
+
st.session_state["attachment_names"] = []
|
|
263
326
|
|
|
264
327
|
|
|
265
328
|
def _render_chat_history() -> None:
|
|
@@ -293,7 +356,10 @@ def _render_chat_history() -> None:
|
|
|
293
356
|
|
|
294
357
|
|
|
295
358
|
def _handle_user_message(
|
|
296
|
-
prompt: str,
|
|
359
|
+
prompt: str,
|
|
360
|
+
config: StreamlitAppConfig,
|
|
361
|
+
attachment_paths: list[str] | None = None,
|
|
362
|
+
attachment_names: list[str] | None = None,
|
|
297
363
|
) -> None:
|
|
298
364
|
"""Process user input and generate assistant response.
|
|
299
365
|
|
|
@@ -309,6 +375,8 @@ def _handle_user_message(
|
|
|
309
375
|
Loaded configuration with response handler definition.
|
|
310
376
|
attachment_paths : list[str] or None, default None
|
|
311
377
|
Optional list of file paths to attach to the message.
|
|
378
|
+
attachment_names : list[str] or None, default None
|
|
379
|
+
Optional list of original filenames for display purposes.
|
|
312
380
|
|
|
313
381
|
Notes
|
|
314
382
|
-----
|
|
@@ -316,11 +384,15 @@ def _handle_user_message(
|
|
|
316
384
|
chat transcript rather than crashing the application. The function
|
|
317
385
|
triggers a Streamlit rerun after successful response generation.
|
|
318
386
|
"""
|
|
319
|
-
|
|
320
|
-
|
|
387
|
+
# Use provided display names or fall back to extracting from paths
|
|
388
|
+
display_names = (
|
|
389
|
+
attachment_names
|
|
390
|
+
if attachment_names
|
|
391
|
+
else [Path(p).name for p in attachment_paths] if attachment_paths else []
|
|
321
392
|
)
|
|
393
|
+
|
|
322
394
|
st.session_state["chat_history"].append(
|
|
323
|
-
{"role": "user", "content": prompt, "attachments":
|
|
395
|
+
{"role": "user", "content": prompt, "attachments": display_names}
|
|
324
396
|
)
|
|
325
397
|
try:
|
|
326
398
|
response = _get_response_instance(config)
|
|
@@ -388,40 +460,63 @@ def main(config_path: Path) -> None:
|
|
|
388
460
|
|
|
389
461
|
_render_chat_history()
|
|
390
462
|
|
|
391
|
-
# File uploader
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
463
|
+
# File uploader form - auto-clears on submit
|
|
464
|
+
with st.form("file_upload_form", clear_on_submit=True):
|
|
465
|
+
uploaded_files = st.file_uploader(
|
|
466
|
+
"Attach files (optional)",
|
|
467
|
+
accept_multiple_files=True,
|
|
468
|
+
help=f"Supported formats: {', '.join(sorted(SUPPORTED_FILE_EXTENSIONS))}",
|
|
469
|
+
)
|
|
470
|
+
submit_files = st.form_submit_button("Attach files")
|
|
398
471
|
|
|
399
|
-
#
|
|
472
|
+
# Process uploaded files if form was submitted
|
|
400
473
|
attachment_paths: list[str] = []
|
|
401
|
-
|
|
474
|
+
original_filenames: list[str] = []
|
|
475
|
+
if submit_files and uploaded_files:
|
|
402
476
|
invalid_files = []
|
|
403
477
|
for uploaded_file in uploaded_files:
|
|
404
478
|
if not _validate_file_type(uploaded_file.name):
|
|
405
479
|
invalid_files.append(uploaded_file.name)
|
|
406
480
|
continue
|
|
481
|
+
|
|
482
|
+
# Create temporary file with the uploaded content
|
|
407
483
|
with tempfile.NamedTemporaryFile(
|
|
408
484
|
delete=False, suffix=Path(uploaded_file.name).suffix
|
|
409
485
|
) as tmp_file:
|
|
410
486
|
tmp_file.write(uploaded_file.getbuffer())
|
|
487
|
+
tmp_file.flush()
|
|
411
488
|
attachment_paths.append(tmp_file.name)
|
|
489
|
+
original_filenames.append(uploaded_file.name)
|
|
490
|
+
# Track for cleanup
|
|
491
|
+
if tmp_file.name not in st.session_state.get("temp_file_paths", []):
|
|
492
|
+
st.session_state["temp_file_paths"].append(tmp_file.name)
|
|
412
493
|
|
|
413
494
|
if invalid_files:
|
|
414
495
|
st.warning(
|
|
415
|
-
f"⚠️ Unsupported file types
|
|
416
|
-
f"Supported
|
|
496
|
+
f"⚠️ Unsupported file types: {', '.join(invalid_files)}. "
|
|
497
|
+
f"Supported: {', '.join(sorted(SUPPORTED_FILE_EXTENSIONS))}"
|
|
417
498
|
)
|
|
418
499
|
if attachment_paths:
|
|
419
|
-
st.
|
|
500
|
+
st.session_state["current_attachments"] = attachment_paths
|
|
501
|
+
st.session_state["attachment_names"] = original_filenames
|
|
502
|
+
st.info(f"📎 {len(attachment_paths)} file(s) attached")
|
|
503
|
+
|
|
504
|
+
# Get attachment paths from session state if they were previously attached
|
|
505
|
+
attachment_paths = st.session_state.get("current_attachments", [])
|
|
506
|
+
attachment_display_names = st.session_state.get("attachment_names", [])
|
|
507
|
+
if attachment_paths:
|
|
508
|
+
st.caption(f"Ready to send: {', '.join(attachment_display_names)}")
|
|
420
509
|
|
|
421
510
|
prompt = st.chat_input("Message the assistant")
|
|
422
511
|
if prompt:
|
|
512
|
+
# Clear attachments before rerun to prevent them from being sent again
|
|
513
|
+
st.session_state["current_attachments"] = []
|
|
514
|
+
st.session_state["attachment_names"] = []
|
|
423
515
|
_handle_user_message(
|
|
424
|
-
prompt,
|
|
516
|
+
prompt,
|
|
517
|
+
config,
|
|
518
|
+
attachment_paths or None,
|
|
519
|
+
attachment_display_names or None,
|
|
425
520
|
)
|
|
426
521
|
|
|
427
522
|
|
|
@@ -41,7 +41,7 @@ openai_sdk_helpers/response/runner.py,sha256=Rf13cQGsR7sN9gA81Y5th1tfH2DCCAwQ6RM
|
|
|
41
41
|
openai_sdk_helpers/response/tool_call.py,sha256=VYPvKUR-Ren0Y_nYS4jUSinhTyXKzFwQLxu-d3r_YuM,4506
|
|
42
42
|
openai_sdk_helpers/response/vector_store.py,sha256=MyHUu6P9ueNsd9erbBkyVqq3stLK6qVuehdvmFAHq9E,3074
|
|
43
43
|
openai_sdk_helpers/streamlit_app/__init__.py,sha256=RjJbnBDS5_YmAmxvaa3phB5u9UcXsXDEk_jMlY_pa5Q,793
|
|
44
|
-
openai_sdk_helpers/streamlit_app/app.py,sha256=
|
|
44
|
+
openai_sdk_helpers/streamlit_app/app.py,sha256=ejJGuy5WNuUXapxlZnYkPWz0gyQdF77l-gOFOzzDtyA,17402
|
|
45
45
|
openai_sdk_helpers/streamlit_app/config.py,sha256=EK6LWACo7YIkDko1oesvupOx56cTuWWnwnXRiu8EYbs,15986
|
|
46
46
|
openai_sdk_helpers/streamlit_app/streamlit_web_search.py,sha256=0RjB545dIvEeZiiLWM7C4CufbD3DITOWLZEVgxAL6mo,2812
|
|
47
47
|
openai_sdk_helpers/structure/__init__.py,sha256=QUvRdJMbKsumjwJdWq9ihfcOED4ZbJMBQbmA1nmYJVw,3339
|
|
@@ -72,8 +72,8 @@ openai_sdk_helpers/vector_storage/__init__.py,sha256=L5LxO09puh9_yBB9IDTvc1CvVkA
|
|
|
72
72
|
openai_sdk_helpers/vector_storage/cleanup.py,sha256=ImWIE-9lli-odD8qIARvmeaa0y8ZD4pYYP-kT0O3178,3552
|
|
73
73
|
openai_sdk_helpers/vector_storage/storage.py,sha256=1juu3Qq6hy33afvVfQeI5A35fQzIPjVZumZ-aP_MxhU,23305
|
|
74
74
|
openai_sdk_helpers/vector_storage/types.py,sha256=jTCcOYMeOpZWvcse0z4T3MVs-RBOPC-fqWTBeQrgafU,1639
|
|
75
|
-
openai_sdk_helpers-0.1.
|
|
76
|
-
openai_sdk_helpers-0.1.
|
|
77
|
-
openai_sdk_helpers-0.1.
|
|
78
|
-
openai_sdk_helpers-0.1.
|
|
79
|
-
openai_sdk_helpers-0.1.
|
|
75
|
+
openai_sdk_helpers-0.1.4.dist-info/METADATA,sha256=xYqGRG7IMVPkN-THuqKYxN8wwZVrDlldfXV0MDwIb38,23557
|
|
76
|
+
openai_sdk_helpers-0.1.4.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
77
|
+
openai_sdk_helpers-0.1.4.dist-info/entry_points.txt,sha256=gEOD1ZeXe8d2OP-KzUlG-b_9D9yUZTCt-GFW3EDbIIY,63
|
|
78
|
+
openai_sdk_helpers-0.1.4.dist-info/licenses/LICENSE,sha256=CUhc1NrE50bs45tcXF7OcTQBKEvkUuLqeOHgrWQ5jaA,1067
|
|
79
|
+
openai_sdk_helpers-0.1.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|