File size: 9,402 Bytes
3efb860
91b6eee
 
df6b720
c29d4f3
4292030
3efb860
91b6eee
 
 
4292030
 
91b6eee
4292030
 
eb27908
 
 
 
 
 
 
 
 
 
 
91b6eee
3efb860
eb27908
91b6eee
 
 
 
c29d4f3
 
91b6eee
 
4292030
c29d4f3
 
 
 
 
 
b3f4796
 
c29d4f3
b3f4796
 
 
 
 
 
 
c29d4f3
b3f4796
 
91b6eee
c29d4f3
 
 
 
 
 
91b6eee
b3f4796
16ea1b0
c29d4f3
 
0da3a2f
 
 
c29d4f3
 
 
 
 
 
 
0da3a2f
c29d4f3
 
 
 
 
 
0da3a2f
c29d4f3
 
 
4292030
c29d4f3
 
 
 
 
0da3a2f
 
 
c29d4f3
 
 
 
 
 
 
 
4292030
c29d4f3
 
 
 
 
 
 
 
0da3a2f
c29d4f3
 
 
 
 
 
0da3a2f
df6b720
c29d4f3
 
91b6eee
b3f4796
91b6eee
 
 
 
4292030
 
91b6eee
4292030
91b6eee
eb27908
 
 
 
 
 
 
 
 
 
91b6eee
 
3efb860
eb27908
c29d4f3
16ea1b0
b3f4796
 
 
 
 
 
c29d4f3
 
 
 
 
 
 
 
 
 
 
 
3efb860
c29d4f3
 
 
 
 
 
3efb860
91b6eee
c29d4f3
 
 
 
 
 
 
 
 
b3f4796
91b6eee
c29d4f3
 
 
91b6eee
4292030
c29d4f3
16ea1b0
3efb860
b3f4796
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
import gradio as gr
import torch
import os
import chardet
import time
from core.summarizer import NarrativeSummarizer

# Models available
MODEL_OPTIONS = [
    "facebook/bart-large-cnn",
    "sshleifer/distilbart-cnn-12-6",
    "google/pegasus-cnn_dailymail",
    "google/long-t5-local-base",
    "t5-small",
    "t5-base",
    "mistralai/Mistral-7B-v0.1",
    "Lewdiculous/Captain-Eris_Violet-V0.420-12B-GGUF-ARM-Imatrix",
    "Lewdiculous/Lumimaid-v0.2-12B-GGUF-IQ-Imatrix",
    "Lewdiculous/Lumimaid-v0.2-8B-GGUF-IQ-Imatrix",
    "Lewdiculous/L3-8B-Stheno-v3.1-GGUF-IQ-Imatrix",
    "Lewdiculous/Llama-3-Lumimaid-8B-v0.1-OAS-GGUF-IQ-Imatrix",
    "Lewdiculous/Nyanade_Stunna-Maid-7B-v0.2-GGUF-IQ-Imatrix",
    "Lewdiculous/InfinityRP-v1-7B-GGUF-IQ-Imatrix",
    "Lewdiculous/Kunoichi-DPO-v2-7B-GGUF-Imatrix",
    "Lewdiculous/BuRP_7B-GGUF-IQ-Imatrix",
    "Lewdiculous/Layris_9B-GGUF-IQ-Imatrix"
]


# Prompt options
PROMPT_OPTIONS = [
    "Bread only",
    "Butter only",
    "Bread and Butter",
    "Custom Prompt"
]

def run_app(file_obj, text_input, model_name, local_model_path, prompt_type, custom_prompt_text, iterations, batch_size, progress=gr.Progress()):
    start_time = time.time()
    
    # Check if custom prompt is selected but not provided
    if prompt_type == "Custom Prompt" and not custom_prompt_text:
        return "❌ Error: 'Custom Prompt' selected but no custom prompt provided.", "", "", None

    # Determine the input source: file or direct text
    if file_obj is not None:
        progress(0, desc="Reading file and detecting encoding...")
        try:
            with open(file_obj.name, 'rb') as f:
                raw_data = f.read()
            detected = chardet.detect(raw_data)
            encoding = detected['encoding'] or 'utf-8'
            text = raw_data.decode(encoding, errors='replace')
        except Exception as e:
            return f"❌ Unable to read the file: {str(e)}", "", "", None
    elif text_input:
        text = text_input
    else:
        return "❌ Please upload a file or enter text to summarize.", "", "", None

    input_word_count = len(text.split())
    
    # Override model name with local path if provided
    actual_model_name = local_model_path if local_model_path else model_name

    # Instantiate the summarizer and process the text
    try:
        progress(0.1, desc=f"Loading model: {actual_model_name}...")
        summarizer = NarrativeSummarizer(model_name=actual_model_name)
        
        chunks = summarizer.chunk_text_tokenwise(text, max_tokens=512, overlap=50)
        total_chunks = len(chunks)
        
        log_messages = [
            f"βœ… Input ready. Word Count: {input_word_count}",
            f"βœ… Using model: {actual_model_name}",
            f"βœ… Split into {total_chunks} chunks.",
            f"βœ… Beginning summarization with {iterations} passes..."
        ]

        condensed_chunks = []
        for i in range(0, total_chunks, batch_size):
            batch = chunks[i:i + batch_size]
            progress(i / total_chunks, desc=f"Processing batch {i // batch_size + 1} of {total_chunks // batch_size + 1}...")
            
            # This is where the actual summarization happens for a single batch
            for _ in range(iterations):
                batch_summaries = summarizer.summarize_batch(
                    batch, 
                    prompt_type, 
                    custom_prompt_text if prompt_type == "Custom Prompt" else None
                )
                batch = batch_summaries
            condensed_chunks.extend(batch)
        
        log_messages.append(f"βœ… Pass 1 complete. Combining summaries...")

        # Second pass for global compression
        combined = " ".join(condensed_chunks)
        
        final_summary = combined
        # Check if the combined text is large enough for a final summary
        if len(summarizer.tokenizer.encode(combined)) > summarizer.tokenizer.model_max_length * 0.8:
            log_messages.append("βœ… Final text is large, performing global summarization...")
            final_summary = summarizer.summarize_batch(
                [combined], 
                prompt_type, 
                custom_prompt_text if prompt_type == "Custom Prompt" else None
            )[0]

        end_time = time.time()
        duration = round(end_time - start_time, 2)
        
        summary_word_count = len(final_summary.split())
        log_messages.append(f"βœ… Summarization complete in {duration} seconds.")
        log_messages.append(f"βœ… Final Summary Word Count: {summary_word_count}")

        # Gradio now handles file downloads
        output_file_path = "summary.txt"
        with open(output_file_path, "w", encoding="utf-8") as f:
            f.write(final_summary)

        return final_summary, "βœ…\n" + "\n".join(log_messages), output_file_path, gr.Button(value="Summarize", interactive=True)

    except Exception as e:
        log_messages.append(f"❌ An error occurred: {str(e)}")
        return f"An error occurred during summarization: {str(e)}", "\n".join(log_messages), None, gr.Button(value="Summarize", interactive=True)

# Gradio Interface
model_tip = gr.Markdown(
    """
    **Model Selection Tips:**
    - **facebook/bart-large-cnn:** Fast, general-purpose summarization for short to medium texts.
    - **sshleifer/distilbart-cnn-12-6:** A smaller, faster version of BART.
    - **google/pegasus-cnn_dailymail:** Excellent for abstractive summarization.
    - **google/long-t5-local-base:** Designed for long documents; better context handling.
    - **t5-small/t5-base:** Efficient and versatile for various tasks.
    - **mistralai/Mistral-7B-v0.1:** High-quality nuanced summaries; resource-intensive.
    - **Lewdiculous/Captain-Eris_Violet-V0.420-12B-GGUF-ARM-Imatrix:** Great for **world-building and immersive roleplay** with coherent, dynamic narratives.
    - **Lewdiculous/Lumimaid-v0.2-12B-GGUF-IQ-Imatrix:** High-quality **world generation** with **creative control** for better safety and versatility.
    - **Lewdiculous/Lumimaid-v0.2-8B-GGUF-IQ-Imatrix:** Versatile model for **roleplay and world-building**, with an emphasis on creative flexibility.
    - **Lewdiculous/L3-8B-Stheno-v3.1-GGUF-IQ-Imatrix:** **Optimized for 1-on-1 roleplay**, handling **scenarios, RPGs**, and **storywriting**.
    - **Lewdiculous/Llama-3-Lumimaid-8B-v0.1-OAS-GGUF-IQ-Imatrix:** **Versatile for roleplay** with **unrestricted generation**, perfect for dynamic worlds.
    - **Lewdiculous/Nyanade_Stunna-Maid-7B-v0.2-GGUF-IQ-Imatrix:** **Multimodal model** with **vision capabilities**, ideal for **image-based and text-based roleplay**.
    - **Lewdiculous/InfinityRP-v1-7B-GGUF-IQ-Imatrix:** Designed for a **cozy roleplay experience** with reduced hallucinations, focused on more grounded conversations.
    - **Lewdiculous/Kunoichi-DPO-v2-7B-GGUF-Imatrix:** **Small, smart model** for **quick roleplay interactions** with thoughtful responses.
    - **Lewdiculous/BuRP_7B-GGUF-IQ-Imatrix:** Great for **unconventional roleplay** with **minimal refusals** and the ability to handle unique dialogue formats.
    - **Lewdiculous/Layris_9B-GGUF-IQ-Imatrix:** A **merged model** with **chaotic and unhinged creativity**, perfect for **dynamic and free-form roleplay**.
    """
)


with gr.Blocks(css="#status-log { overflow-y: scroll; max-height: 200px; }") as demo:
    gr.Markdown("# Narrative Summarizer")
    gr.Markdown("Upload your text file OR enter text below to get a summarized version.")

    with gr.Row():
        file_input = gr.File(label="Upload Text File (.txt)", file_types=['.txt'])
        text_input = gr.Textbox(label="Or, paste your text here", lines=10)

    gr.Markdown("---")
    
    with gr.Accordion("Model & Prompt Settings", open=True):
        with gr.Row():
            model_dropdown = gr.Dropdown(choices=MODEL_OPTIONS, label="Choose Model", value=MODEL_OPTIONS[0])
            local_model_path_input = gr.Textbox(label="Local Model Path (optional)")
        
        model_tip.render()
        
        with gr.Row():
            prompt_dropdown = gr.Dropdown(choices=PROMPT_OPTIONS, label="Choose Prompt Type", value=PROMPT_OPTIONS[0])
            custom_prompt_input = gr.Textbox(label="Custom Prompt (use {chunk} placeholder)")

    gr.Markdown("---")

    with gr.Accordion("Advanced Parameters", open=False):
        with gr.Row():
            iterations_slider = gr.Slider(minimum=1, maximum=5, step=1, label="Summarization Iterations", value=1)
            batch_size_slider = gr.Slider(minimum=1, maximum=8, step=1, label="Batch Size (for GPU)", value=4)

    summarize_button = gr.Button("Summarize")
    
    with gr.Row():
        output_text = gr.Textbox(label="Summary Output", lines=15)
        status_log = gr.Textbox(label="Process Log", lines=15, interactive=False, elem_id="status-log")
    
    download_button = gr.File(label="Download Summary", file_types=['.txt'])

    def update_ui_on_click():
        return gr.Button(interactive=False)

    summarize_button.click(
        fn=update_ui_on_click,
        outputs=summarize_button
    ).then(
        fn=run_app,
        inputs=[file_input, text_input, model_dropdown, local_model_path_input, prompt_dropdown, custom_prompt_input, iterations_slider, batch_size_slider],
        outputs=[output_text, status_log, download_button, summarize_button]
    )

demo.launch()