wolf1997 commited on
Commit
890a83b
·
verified ·
1 Parent(s): fe5ca79

Upload 3 files

Browse files
Files changed (3) hide show
  1. receipt_gen_agent.py +260 -0
  2. requirements.txt +10 -0
  3. streamlit.py +326 -0
receipt_gen_agent.py ADDED
@@ -0,0 +1,260 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_core.output_parsers import JsonOutputParser
2
+ from langchain_core.prompts import PromptTemplate
3
+ from dotenv import load_dotenv
4
+ import os
5
+ from typing import List
6
+ from typing_extensions import TypedDict
7
+ from langchain_core.messages import HumanMessage
8
+ from langchain_google_genai import ChatGoogleGenerativeAI
9
+ from langchain.output_parsers import RetryOutputParser
10
+ from langgraph.graph import StateGraph, START, END
11
+ import base64
12
+ from IPython.display import Image as img, display
13
+ from langchain_core.runnables.graph import MermaidDrawMethod
14
+ from langgraph.checkpoint.memory import MemorySaver
15
+ import json
16
+ from pydantic import BaseModel, Field
17
+ from io import BytesIO
18
+ load_dotenv()
19
+ GEMINI_API_KEY=os.getenv('google_api_key')
20
+
21
+
22
+ GEMINI_MODEL='gemini-2.0-flash'
23
+ llm = ChatGoogleGenerativeAI(google_api_key=GEMINI_API_KEY, model=GEMINI_MODEL, temperature=0.3)
24
+
25
+ from os import listdir
26
+ from os.path import isfile, join
27
+
28
+
29
+ class State(TypedDict):
30
+ prompt: str
31
+ image_number: int
32
+ image_data: json
33
+ image_byte: str
34
+ eval: dict
35
+ n_retries:int
36
+ image_name: str
37
+ image_data_list: list
38
+
39
+
40
+ def generate_data_node(state:State):
41
+ class Items(BaseModel):
42
+ name: str = Field(description='the name of the item')
43
+ price : float = Field(description='the price of the item')
44
+ quantity: int = Field(description='the quantity of the item')
45
+
46
+ class Form(BaseModel):
47
+ loc_name: str = Field(description='the name of the location if no name put empty str')
48
+ address: str = Field(description='the address of the location if no location put empty str')
49
+ date: str = Field(description='the date if no date put empty str')
50
+ time: str = Field(description='the time if no time put empty str')
51
+ items: List[Items] = Field(description= 'list of the items if no items put empty list')
52
+ subtotal: float = Field(description= 'the subtotal if no subtotal put 0')
53
+ tax: float = Field(description='the tax, if no tax put 0')
54
+ total: float = Field(description='the total amount if no total amount put 0')
55
+
56
+
57
+ parser=JsonOutputParser(pydantic_object=Form)
58
+ instruction=parser.get_format_instructions()
59
+ message = HumanMessage(
60
+ content=[
61
+ {"type": "text", "text": f"{state.get('prompt')}"+'\n\n'+ instruction},
62
+ {
63
+ "type": "image_url",
64
+ "image_url": {"url": f"data:image/jpeg;base64,{state.get('image_byte')}"},
65
+ },
66
+ ],
67
+ )
68
+ response=llm.invoke([message])
69
+ try:
70
+ response=parser.parse(response.content)
71
+ return {'image_data':response}
72
+ except:
73
+ prompt = PromptTemplate(
74
+ template="Answer the user query.\n{format_instructions}\n{query}\n",
75
+ input_variables=["query"],
76
+ partial_variables={"format_instructions": parser.get_format_instructions()},
77
+ )
78
+ retry_parser = RetryOutputParser.from_llm(parser=parser, llm=llm)
79
+ prompt_value=prompt.format_prompt(query=f'{state.get('prompt')}')
80
+ response=retry_parser.parse_with_prompt(response.content, prompt_value)
81
+ return {'image_data':response}
82
+
83
+ def evaluate_node(state:State):
84
+
85
+
86
+ class Decision(BaseModel):
87
+ decision: str = Field(description='good or modify if changes have to be made')
88
+ comment: str = Field(description='the changes to make')
89
+
90
+ parser=JsonOutputParser(pydantic_object=Decision)
91
+ prompt = PromptTemplate(
92
+ template="Answer the user query.\n{format_instructions}\n{query}\n",
93
+ input_variables=["query"],
94
+ partial_variables={"format_instructions": parser.get_format_instructions()},
95
+ )
96
+ data=state.get('image_data')
97
+ query=f" is the {data} correct and makes sense tell the llm what to change, ignore missing data, don't make it up, no explanation or decription needed"
98
+ chain = prompt | llm
99
+ response=chain.invoke({'query':query})
100
+ try:
101
+ response=parser.parse(response.content)
102
+ except:
103
+
104
+ retry_parser = RetryOutputParser.from_llm(parser=parser, llm=llm)
105
+
106
+ prompt_value = prompt.format_prompt(query=query)
107
+ response=retry_parser.parse_with_prompt(response.content, prompt_value)
108
+ return {'eval': response}
109
+
110
+
111
+ def data_editor_node(state:State):
112
+ class Items(BaseModel):
113
+ name: str = Field(description='the name of the item')
114
+ price : float = Field(description='the price of the item')
115
+ quantity: int = Field(description='the quantity of the item')
116
+
117
+ class Form(BaseModel):
118
+ loc_name: str = Field(description='the name of the location if no name put empty str')
119
+ address: str = Field(description='the address of the location if no location put empty str')
120
+ date: str = Field(description='the date if no date put empty str')
121
+ time: str = Field(description='the time if no time put empty str')
122
+ items: List[Items] = Field(description= 'list of the items if no items put empty list')
123
+ subtotal: float = Field(description= 'the subtotal if no subtotal put 0')
124
+ tax: float = Field(description='the tax, if no tax put 0')
125
+ total: float = Field(description='the total amount if no total amount put 0')
126
+
127
+
128
+ parser=JsonOutputParser(pydantic_object=Form)
129
+ prompt = PromptTemplate(
130
+ template="Answer the user query.\n{format_instructions}\n{query}\n",
131
+ input_variables=["query"],
132
+ partial_variables={"format_instructions": parser.get_format_instructions()},
133
+ )
134
+
135
+
136
+ data=state.get('image_data')
137
+ query=f"modify this dict: {data} based on these comments {state.get('eval').get('comment')}, return a json"
138
+ chain = prompt | llm
139
+ response=chain.invoke({'query':query})
140
+ try:
141
+ response=parser.parse(response.content)
142
+ except:
143
+
144
+ retry_parser = RetryOutputParser.from_llm(parser=parser, llm=llm)
145
+
146
+ prompt_value = prompt.format_prompt(query=query)
147
+ response=retry_parser.parse_with_prompt(response.content, prompt_value)
148
+ return {'image_data': response,
149
+ 'n_retries':state.get('n_retries')+1}
150
+
151
+
152
+ def should_continue(state:State)-> str:
153
+ """
154
+ Determine whether the research process should continue based on the current state.
155
+
156
+ Args:
157
+ state: The current state of the agent.
158
+
159
+ Returns:
160
+ str: The next state to transition to ("to_add_data", "to_prompt_editor").
161
+ """
162
+ eval=state.get('eval').get('decision')
163
+ if eval =='good':
164
+ return 'to_add_data'
165
+
166
+ elif eval =='modify' and state.get('n_retries')<2:
167
+ return 'to_data_editor'
168
+ else:
169
+ return 'to_add_data'
170
+
171
+
172
+ def add_data_node(state:State):
173
+ img_number=state.get('image_number')
174
+ return {
175
+ 'n_retries':0,
176
+
177
+ 'image_name':f'{img_number}_new_receipt.jpg'}
178
+
179
+ class receipt_agent:
180
+ def __init__(self):
181
+ self.agent=self._setup()
182
+ def _setup(self):
183
+
184
+ agent_builder=StateGraph(State)
185
+ agent_builder.add_node('generate_data',generate_data_node)
186
+ agent_builder.add_node('evaluate',evaluate_node)
187
+ agent_builder.add_node('add_data',add_data_node)
188
+ agent_builder.add_node('data_editor',data_editor_node)
189
+
190
+ agent_builder.add_edge(START,'generate_data')
191
+ agent_builder.add_edge('generate_data','evaluate')
192
+ # agent_builder.add_edge('evaluate',END)
193
+ agent_builder.add_conditional_edges('evaluate', should_continue, {'to_data_editor':'data_editor', 'to_add_data':'add_data'},)
194
+ agent_builder.add_edge('data_editor','evaluate')
195
+ agent_builder.add_edge('add_data', END)
196
+
197
+
198
+ checkpointer=MemorySaver()
199
+
200
+ agent=agent_builder.compile(checkpointer=checkpointer)
201
+ return agent
202
+
203
+
204
+ def display_graph(self):
205
+ return display(
206
+ img(
207
+ self.agent.get_graph().draw_mermaid_png(
208
+ draw_method=MermaidDrawMethod.API,
209
+ )
210
+ )
211
+ )
212
+
213
+ def get_state(self, state_val:str):
214
+ config = {"configurable": {"thread_id": "1"}}
215
+ return self.agent.get_state(config).values[state_val]
216
+
217
+ def receipt_gen(self,image):
218
+ config = {"configurable": {"thread_id": "1"}}
219
+ buffered=BytesIO()
220
+
221
+ image.save(buffered, format='JPEG')
222
+ image_data = base64.b64encode(buffered.getvalue()).decode("utf-8")
223
+
224
+ data_list = [f for f in listdir('new_receipt_data') if isfile(join('new_receipt_data', f))]
225
+ if not data_list:
226
+ data_list=[]
227
+ else:
228
+ with open(f'new_receipt_data/{data_list[0]}', 'r') as openfile:
229
+ # Reading from json file
230
+ data_list = json.load(openfile)
231
+
232
+ response=self.agent.invoke({'prompt':'analyse this receipt and list the items, return a json',
233
+ 'n_retries':0,
234
+ 'image_number':len(data_list),
235
+ 'image_byte': image_data,
236
+ 'image_data_list':data_list}, config)
237
+
238
+ image_data=response.get('image_data')
239
+ return image_data
240
+
241
+ def update_state(self, values:dict):
242
+ config = {"configurable": {"thread_id": "1"}}
243
+ return self.agent.update_state(config,values=values)
244
+
245
+ def confirm(self,image_data):
246
+ config = {"configurable": {"thread_id": "1"}}
247
+ if image_data:
248
+ data_list=self.agent.get_state(config).values['image_data_list']
249
+ img_number=self.agent.get_state(config).values['image_number']
250
+ image_name=self.agent.get_state(config).values['image_name']
251
+ if not data_list:
252
+ data_list=[]
253
+ data_list.append({'receipt_name':f'{img_number}_new_receipt.jpg',
254
+ 'receipt_data':image_data})
255
+ self.agent.update_state(config,values={'image_data_list':data_list})
256
+
257
+
258
+ return data_list,image_name
259
+
260
+
requirements.txt ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ ipython==9.0.1
2
+ langchain==0.3.20
3
+ langchain_core==0.3.41
4
+ langchain_google_genai==2.0.11
5
+ langgraph==0.3.5
6
+ Pillow==11.1.0
7
+ pydantic==2.10.6
8
+ python-dotenv==1.0.1
9
+ typing_extensions==4.12.2
10
+ streamlit==1.43.0
streamlit.py ADDED
@@ -0,0 +1,326 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import json
3
+ import copy
4
+ from receipt_gen_agent import receipt_agent
5
+ from PIL import Image
6
+
7
+ from os import listdir
8
+ from os.path import isfile, join
9
+ agent=receipt_agent()
10
+ def capture_or_upload_image():
11
+ """
12
+ Provides options to capture or upload an image using Streamlit.
13
+
14
+ Returns:
15
+ PIL.Image.Image or None: The captured/uploaded image
16
+ """
17
+ st.sidebar.header("📸 Image Capture / Upload")
18
+
19
+ # Choose method
20
+ image_source = st.sidebar.radio(
21
+ "Choose Image Source",
22
+ ["Upload", "Camera Capture"],
23
+ key="image_source"
24
+ )
25
+
26
+ # Image upload
27
+ if image_source == "Upload":
28
+ uploaded_file = st.sidebar.file_uploader(
29
+ "Upload Receipt Image",
30
+ type=['png', 'jpg', 'jpeg', 'gif'],
31
+ help="Upload an image of the receipt",
32
+ key="uploaded_file"
33
+ )
34
+
35
+ if uploaded_file is not None:
36
+ # Convert to PIL Image
37
+ pil_image = Image.open(uploaded_file)
38
+ # Store in session state
39
+ st.session_state.receipt_image = pil_image
40
+ return pil_image
41
+
42
+ # Camera capture
43
+ else:
44
+ captured_image = st.camera_input(
45
+ "Take a picture of the receipt",
46
+ key="camera_input"
47
+ )
48
+
49
+ if captured_image is not None:
50
+ # Convert to PIL Image
51
+ pil_image = Image.open(captured_image)
52
+ # Store in session state
53
+ st.session_state.receipt_image = pil_image
54
+ return pil_image
55
+
56
+ # Return image from session state if available
57
+ return st.session_state.get("receipt_image", None)
58
+
59
+ def parse_receipt_data(receipt_str):
60
+ """Parse the receipt dictionary from a string or dict."""
61
+ if isinstance(receipt_str, str):
62
+ try:
63
+ return json.loads(receipt_str.replace("'", '"'))
64
+ except json.JSONDecodeError:
65
+ return {}
66
+ return receipt_str
67
+
68
+ def receipt_editor_app():
69
+ # Initialize session state variables if they don't exist
70
+ if "receipt_dict" not in st.session_state:
71
+ # Default initial receipt data
72
+ initial_receipt_str = {
73
+ "loc_name": "someplace",
74
+ "address": "somewhere",
75
+ "date": "someday",
76
+ "time": "sometime",
77
+ "items": [
78
+ {
79
+ "name": "something",
80
+ "price": 3.0,
81
+ "quantity": 1
82
+ }
83
+ ],
84
+ "subtotal": 51.9,
85
+ "tax": 4.68,
86
+ "total": 56.58
87
+ }
88
+ st.session_state.receipt_dict = parse_receipt_data(initial_receipt_str)
89
+ st.session_state.edited_receipt = copy.deepcopy(st.session_state.receipt_dict)
90
+
91
+ if "receipt_image" not in st.session_state:
92
+ st.session_state.receipt_image = None
93
+
94
+ if "show_receipt_image" not in st.session_state:
95
+ st.session_state.show_receipt_image = True
96
+
97
+ # Set page title and layout
98
+ st.set_page_config(page_title="Receipt Editor", layout="wide")
99
+
100
+ # Capture or upload image
101
+ receipt_image = capture_or_upload_image()
102
+
103
+ if receipt_image is not None:
104
+ # Option to display image
105
+ show_image = st.sidebar.checkbox(
106
+ "Show Receipt Image",
107
+ value=st.session_state.show_receipt_image,
108
+ key="show_image_checkbox"
109
+ )
110
+ st.session_state.show_receipt_image = show_image
111
+
112
+ if show_image:
113
+ # Create two columns for image and details
114
+ col1, col2 = st.sidebar.columns([1, 1])
115
+
116
+ with col1:
117
+ # Resize image to fit sidebar
118
+ resized_image = receipt_image.copy()
119
+ resized_image.thumbnail((600, 600))
120
+ st.image(resized_image, caption="Uploaded Receipt")
121
+
122
+ with col2:
123
+ # Basic image details
124
+ st.write(f"Image Size: {receipt_image.size}")
125
+ st.write(f"Image Format: {receipt_image.format if hasattr(receipt_image, 'format') else 'Unknown'}")
126
+ st.write(f"Color Mode: {receipt_image.mode}")
127
+
128
+ if st.sidebar.button("Process Image"):
129
+ initial_receipt_str = agent.receipt_gen(receipt_image)
130
+ # Parse initial receipt data
131
+ st.session_state.receipt_dict = parse_receipt_data(initial_receipt_str)
132
+
133
+
134
+ # Update edited receipt in session state
135
+ st.session_state.edited_receipt = copy.deepcopy(st.session_state.receipt_dict)
136
+ st.sidebar.success("Receipt processed!")
137
+
138
+
139
+ # Create a reference to session state for cleaner code
140
+ edited_receipt = st.session_state.edited_receipt
141
+
142
+ # Title
143
+ st.title("Receipt Editor")
144
+
145
+ # Basic Information Section
146
+ st.header("Basic Information")
147
+ col1, col2 = st.columns(2)
148
+ with col1:
149
+ edited_receipt['loc_name'] = st.text_input(
150
+ "Location Name",
151
+ value=edited_receipt['loc_name'],
152
+ key="loc_name"
153
+ )
154
+ edited_receipt['date'] = st.text_input(
155
+ "Date",
156
+ value=edited_receipt['date'],
157
+ key="date"
158
+ )
159
+
160
+ with col2:
161
+ edited_receipt['address'] = st.text_area(
162
+ "Address",
163
+ value=edited_receipt['address'],
164
+ key="address"
165
+ )
166
+ edited_receipt['time'] = st.text_input(
167
+ "Time",
168
+ value=edited_receipt['time'],
169
+ key="time"
170
+ )
171
+
172
+ # Items Section
173
+ st.header("Items")
174
+
175
+ # Expandable section for items
176
+ with st.expander("Edit Items"):
177
+ # Allow adding new items
178
+ add_item = st.checkbox("Add New Item", key="add_item_checkbox")
179
+ if add_item:
180
+ with st.form(key="add_item_form"):
181
+ new_item_name = st.text_input("New Item Name", key="new_item_name")
182
+ new_item_price = st.number_input("New Item Price", min_value=0.0, step=0.01, key="new_item_price")
183
+ new_item_quantity = st.number_input("New Item Quantity", min_value=1, step=1, value=1, key="new_item_quantity")
184
+
185
+ submit_button = st.form_submit_button(label="Add to Receipt")
186
+ if submit_button:
187
+ new_item = {
188
+ 'name': new_item_name,
189
+ 'price': new_item_price,
190
+ 'quantity': new_item_quantity
191
+ }
192
+ edited_receipt['items'].append(new_item)
193
+ st.session_state.edited_receipt = edited_receipt
194
+
195
+
196
+ # Edit existing items
197
+ for i, item in enumerate(edited_receipt['items']):
198
+ with st.expander(f"Item {i+1}: {item['name']}"):
199
+ col1, col2, col3 = st.columns(3)
200
+ with col1:
201
+ item['name'] = st.text_input(
202
+ f"Item {i+1} Name",
203
+ value=item['name'],
204
+ key=f"name_{i}"
205
+ )
206
+ with col2:
207
+ item['price'] = st.number_input(
208
+ f"Item {i+1} Price",
209
+ min_value=0.0,
210
+ value=item['price'],
211
+ step=0.01,
212
+ key=f"price_{i}"
213
+ )
214
+ with col3:
215
+ item['quantity'] = st.number_input(
216
+ f"Item {i+1} Quantity",
217
+ min_value=1,
218
+ value=item['quantity'],
219
+ step=1,
220
+ key=f"quantity_{i}"
221
+ )
222
+
223
+ # Option to remove item
224
+ if st.button(f"Remove Item {i+1}", key=f"remove_{i}"):
225
+ del edited_receipt['items'][i]
226
+ st.session_state.edited_receipt = edited_receipt
227
+
228
+
229
+ # Totals Section
230
+ st.header("Totals")
231
+ col1, col2, col3 = st.columns(3)
232
+ edited_receipt=st.session_state.edited_receipt
233
+ with col1:
234
+ edited_receipt['subtotal'] = st.number_input(
235
+ "Subtotal",
236
+ min_value=0.0,
237
+ value=edited_receipt['subtotal'],
238
+ step=0.01,
239
+ key="subtotal"
240
+ )
241
+
242
+ with col2:
243
+ edited_receipt['tax'] = st.number_input(
244
+ "Tax",
245
+ min_value=0.0,
246
+ value=edited_receipt['tax'],
247
+ step=0.01,
248
+ key="tax"
249
+ )
250
+
251
+ with col3:
252
+ edited_receipt['total'] = st.number_input(
253
+ "Total",
254
+ min_value=0.0,
255
+ value=edited_receipt['total'],
256
+ step=0.01,
257
+ key="total"
258
+ )
259
+
260
+ # Recalculate totals button
261
+ if st.button("Recalculate Totals", key="recalc_button"):
262
+ edited_receipt=st.session_state.edited_receipt
263
+ # Automatically calculate subtotal
264
+ edited_receipt['subtotal'] = sum(item['price'] * item['quantity'] for item in edited_receipt['items'])
265
+
266
+ # Recalculate total
267
+ edited_receipt['total'] = edited_receipt['subtotal'] + edited_receipt['tax']
268
+
269
+ # Update session state
270
+ st.session_state.edited_receipt = edited_receipt
271
+
272
+ # Display final edited receipt
273
+ st.header("Edited Receipt")
274
+ st.json(st.session_state.edited_receipt)
275
+
276
+ # Handle file saving
277
+
278
+ data_list = [f for f in listdir('new_receipt_data') if isfile(join('new_receipt_data', f))]
279
+ if not data_list:
280
+ st.session_state.data_list = []
281
+ else:
282
+ with open(f'new_receipt_data/{data_list[0]}', 'r') as openfile:
283
+ # Reading from json file
284
+ st.session_state.data_list = json.load(openfile)
285
+
286
+
287
+ st.session_state.image_name = f'{len(st.session_state.data_list)+1}_new_receipt.jpg'
288
+
289
+ # Option to save or export
290
+ if st.button("Save Receipt Data", key="save_button"):
291
+ try:
292
+ # Create directories if they don't exist
293
+
294
+ # Save receipt data
295
+ data_list=st.session_state.data_list
296
+ data_list.append({
297
+ 'receipt_name':st.session_state.image_name,
298
+ 'receipt_data': st.session_state.edited_receipt
299
+ })
300
+
301
+ with open("new_receipt_data/receipt_data_list.json", "w") as f:
302
+ json.dump(data_list, f, indent=2)
303
+
304
+ # Save image if available
305
+ if st.session_state.receipt_image:
306
+ save_path = f'new_images/{st.session_state.image_name}'
307
+ st.session_state.receipt_image.save(save_path)
308
+ st.success(f"Receipt data and image saved successfully!")
309
+ else:
310
+ st.success("Receipt data saved (no image available)")
311
+
312
+ except Exception as e:
313
+ st.error(f"Error saving receipt: {str(e)}")
314
+
315
+ # Reset button to clear session state
316
+ if st.sidebar.button("Reset All", key="reset_button"):
317
+ # Clear specific session state values
318
+ for key in list(st.session_state.keys()):
319
+ del st.session_state[key]
320
+
321
+
322
+ def main():
323
+ receipt_editor_app()
324
+
325
+ if __name__ == "__main__":
326
+ main()