File size: 11,244 Bytes
79ac1e4
 
13fd803
 
17ff7d9
a370b95
 
 
79ac1e4
17ff7d9
 
 
79ac1e4
66c5a69
 
 
eb2eaac
 
 
101c783
dab47f5
101c783
 
dab47f5
101c783
 
 
 
 
79ac1e4
 
 
 
 
 
 
 
b44cf1a
 
 
dab47f5
b44cf1a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17ff7d9
79ac1e4
dab47f5
eb2eaac
17ff7d9
 
 
38d1b1a
17ff7d9
 
 
b44cf1a
 
 
101c783
 
 
 
 
b44cf1a
 
 
 
 
 
 
 
 
38d1b1a
02fee92
b44cf1a
 
 
 
 
38d1b1a
b44cf1a
 
 
 
 
 
 
38d1b1a
b44cf1a
0529faa
38d1b1a
0529faa
b44cf1a
 
0529faa
79ac1e4
 
 
0529faa
 
f241c4a
38d1b1a
f241c4a
 
38d1b1a
0529faa
 
 
 
 
 
38d1b1a
 
 
 
 
 
4214877
0e1c90e
4214877
0e1c90e
4214877
 
79ac1e4
02fee92
17ff7d9
 
 
b44cf1a
 
c9a5395
 
79ac1e4
 
 
0e1c90e
 
 
 
4214877
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0529faa
 
 
 
 
79ac1e4
 
 
13fd803
eb2eaac
13fd803
 
79ac1e4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17ff7d9
 
dab47f5
17ff7d9
7e9f871
17ff7d9
 
b44cf1a
 
17ff7d9
 
 
b44cf1a
 
 
 
 
 
 
 
7e9f871
b44cf1a
17ff7d9
 
eb2eaac
17ff7d9
eb2eaac
17ff7d9
02fee92
17ff7d9
eb2eaac
c9a5395
38d1b1a
45cd93b
 
eb2eaac
13fd803
17ff7d9
b44cf1a
 
02fee92
79ac1e4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66e859e
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
import gradio as gr
import markdown
import threading
import time
import logging
from src.core.converter import convert_file, set_cancellation_flag, is_conversion_in_progress
from src.services.docling_chat import chat_with_document
from src.parsers.parser_registry import ParserRegistry

# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

# Add a global variable to track cancellation state
conversion_cancelled = threading.Event()

# Pass the cancellation flag to the converter module
set_cancellation_flag(conversion_cancelled)

# Add a background thread to monitor cancellation
def monitor_cancellation():
    """Background thread to monitor cancellation and update UI if needed"""
    logger.info("Starting cancellation monitor thread")
    while is_conversion_in_progress():
        if conversion_cancelled.is_set():
            logger.info("Cancellation detected by monitor thread")
        time.sleep(0.1)  # Check every 100ms
    logger.info("Cancellation monitor thread ending")

def format_markdown_content(content):
    if not content:
        return content
    
    # Convert the content to HTML using markdown library
    html_content = markdown.markdown(str(content), extensions=['tables'])
    return html_content

# Function to run conversion in a separate thread
def run_conversion_thread(file_path, parser_name, ocr_method_name, output_format):
    """Run the conversion in a separate thread and return the thread object"""
    global conversion_cancelled
    
    # Reset the cancellation flag
    conversion_cancelled.clear()
    
    # Create a container for the results
    results = {"content": None, "download_file": None, "error": None}
    
    def conversion_worker():
        try:
            content, download_file = convert_file(file_path, parser_name, ocr_method_name, output_format)
            results["content"] = content
            results["download_file"] = download_file
        except Exception as e:
            logger.error(f"Error during conversion: {str(e)}")
            results["error"] = str(e)
    
    # Create and start the thread
    thread = threading.Thread(target=conversion_worker)
    thread.daemon = True
    thread.start()
    
    return thread, results

def handle_convert(file_path, parser_name, ocr_method_name, output_format, is_cancelled):
    """Handle file conversion."""
    global conversion_cancelled
    
    # Check if we should cancel before starting
    if is_cancelled:
        logger.info("Conversion cancelled before starting")
        return "Conversion cancelled.", None, gr.update(visible=False), gr.update(visible=True), gr.update(visible=False), None
    
    logger.info("Starting conversion with cancellation flag cleared")
    
    # Start the conversion in a separate thread
    thread, results = run_conversion_thread(file_path, parser_name, ocr_method_name, output_format)
    
    # Start the monitoring thread
    monitor_thread = threading.Thread(target=monitor_cancellation)
    monitor_thread.daemon = True
    monitor_thread.start()
    
    # Wait for the thread to complete or be cancelled
    while thread.is_alive():
        # Check if cancellation was requested
        if conversion_cancelled.is_set():
            logger.info("Cancellation detected, waiting for thread to finish")
            # Give the thread a chance to clean up
            thread.join(timeout=0.5)
            if thread.is_alive():
                logger.warning("Thread did not finish within timeout")
            return "Conversion cancelled.", None, gr.update(visible=False), gr.update(visible=True), gr.update(visible=False), None
        
        # Sleep briefly to avoid busy waiting
        time.sleep(0.1)
    
    # Thread has completed, check results
    if results["error"]:
        return f"Error: {results['error']}", None, gr.update(visible=False), gr.update(visible=True), gr.update(visible=False), None
    
    content = results["content"]
    download_file = results["download_file"]
    
    # If conversion returned a cancellation message
    if content == "Conversion cancelled.":
        logger.info("Converter returned cancellation message")
        return content, None, gr.update(visible=False), gr.update(visible=True), gr.update(visible=False), None
    
    # Format the content and wrap it in the scrollable container
    formatted_content = format_markdown_content(str(content))
    html_output = f"<div class='output-container'>{formatted_content}</div>"
    
    logger.info("Conversion completed successfully")
    return html_output, download_file, gr.update(visible=False), gr.update(visible=True), gr.update(visible=False), None

def create_ui():
    with gr.Blocks(css="""
        /* Simple output container with only one scrollbar */
        .output-container {
            max-height: 420px;  /* Changed from 600px to 70% of original height */
            overflow-y: auto;
            border: 1px solid #ddd;  /* Added border for better visual definition */
            padding: 10px;  /* Added padding for better content spacing */
        }
        
        /* Hide any scrollbars from parent containers */
        .gradio-container .prose {
            overflow: visible;
        }
        
        .processing-controls { 
            display: flex; 
            justify-content: center; 
            gap: 10px; 
            margin-top: 10px; 
        }
        
        /* Add margin above the provider/OCR options row */
        .provider-options-row {
            margin-top: 15px;
            margin-bottom: 15px;
        }
    """) as demo:
        gr.Markdown("Markit: Convert any documents to Markdown")
        
        # State to track if cancellation is requested
        cancel_requested = gr.State(False)
        # State to store the conversion thread
        conversion_thread = gr.State(None)
        # State to store the output format (fixed to Markdown)
        output_format_state = gr.State("Markdown")

        with gr.Tabs():
            with gr.Tab("Upload and Convert"):
                # File input first
                file_input = gr.File(label="Upload PDF", type="filepath")
                
                # Provider and OCR options below the file input
                with gr.Row(elem_classes=["provider-options-row"]):
                    with gr.Column(scale=1):
                        parser_names = ParserRegistry.get_parser_names()
                        default_parser = parser_names[0] if parser_names else "PyPdfium"
                        
                        provider_dropdown = gr.Dropdown(
                            label="Provider",
                            choices=parser_names,
                            value=default_parser,
                            interactive=True
                        )
                    with gr.Column(scale=1):
                        default_ocr_options = ParserRegistry.get_ocr_options(default_parser)
                        default_ocr = default_ocr_options[0] if default_ocr_options else "No OCR"
                        
                        ocr_dropdown = gr.Dropdown(
                            label="OCR Options",
                            choices=default_ocr_options,
                            value=default_ocr,
                            interactive=True
                        )
                
                # Simple output container with just one scrollbar
                file_display = gr.HTML(
                    value="<div class='output-container'></div>",
                    label="Converted Content"
                )
                
                file_download = gr.File(label="Download File")
                
                # Processing controls row
                with gr.Row(elem_classes=["processing-controls"]):
                    convert_button = gr.Button("Convert", variant="primary")
                    cancel_button = gr.Button("Cancel", variant="stop", visible=False)

            with gr.Tab("Chat with Document"):
                document_text_state = gr.State("")
                chatbot = gr.Chatbot(label="Chat", type="messages")
                text_input = gr.Textbox(placeholder="Type here...")
                clear_btn = gr.Button("Clear")

        # Event handlers
        provider_dropdown.change(
            lambda p: gr.Dropdown(choices=ParserRegistry.get_ocr_options(p), 
                                value=ParserRegistry.get_ocr_options(p)[0] if ParserRegistry.get_ocr_options(p) else None),
            inputs=[provider_dropdown],
            outputs=[ocr_dropdown]
        )

        # Reset cancel flag when starting conversion
        def start_conversion():
            global conversion_cancelled
            conversion_cancelled.clear()
            logger.info("Starting conversion with cancellation flag cleared")
            return gr.update(visible=False), gr.update(visible=True), False

        # Set cancel flag and terminate thread when cancel button is clicked
        def request_cancellation(thread):
            global conversion_cancelled
            conversion_cancelled.set()
            logger.info("Cancel button clicked, cancellation flag set")
            
            # Try to join the thread with a timeout
            if thread is not None:
                logger.info(f"Attempting to join conversion thread: {thread}")
                thread.join(timeout=0.5)
                if thread.is_alive():
                    logger.warning("Thread did not finish within timeout")
            
            # Add immediate feedback to the user
            return gr.update(visible=True), gr.update(visible=False), True, None

        # Start conversion sequence
        convert_button.click(
            fn=start_conversion,
            inputs=[],
            outputs=[convert_button, cancel_button, cancel_requested],
            queue=False  # Execute immediately
        ).then(
            fn=handle_convert,
            inputs=[file_input, provider_dropdown, ocr_dropdown, output_format_state, cancel_requested],
            outputs=[file_display, file_download, convert_button, cancel_button, conversion_thread]
        )
        
        # Handle cancel button click
        cancel_button.click(
            fn=request_cancellation,
            inputs=[conversion_thread],
            outputs=[convert_button, cancel_button, cancel_requested, conversion_thread],
            queue=False  # Execute immediately
        )

        file_display.change(
            lambda text: text,
            inputs=[file_display],
            outputs=[document_text_state]
        )

        text_input.submit(
            fn=chat_with_document,
            inputs=[text_input, chatbot, document_text_state],
            outputs=[chatbot, chatbot]
        )

        clear_btn.click(
            lambda: ([], []),
            None,
            [chatbot, chatbot]
        )

    return demo


def launch_ui(server_name="0.0.0.0", server_port=7860, share=False):
    demo = create_ui()
    demo.launch(
        server_name=server_name,
        server_port=server_port,
        root_path="",
        show_error=True,
        share=share
    )