File size: 7,468 Bytes
52caa2a
 
d1f60b0
 
 
 
 
52caa2a
d1f60b0
 
 
 
 
 
 
4994494
d1f60b0
52caa2a
d1f60b0
 
 
 
 
 
 
 
 
 
 
52caa2a
d1f60b0
 
 
 
 
 
 
52caa2a
d1f60b0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52caa2a
 
 
d1f60b0
 
 
 
 
 
 
 
52caa2a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7e7efaf
465ea3f
 
52caa2a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
504e39f
52caa2a
504e39f
52caa2a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
01ad4eb
 
 
 
 
 
52caa2a
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
import gradio as gr
import requests
import json
import os
import time
from collections import defaultdict

BASE_URL = "https://api.jigsawstack.com/v1"
headers = {
    "x-api-key":  os.getenv("JIGSAWSTACK_API_KEY")
}


# Rate limiting configuration
request_times = defaultdict(list)
MAX_REQUESTS = 20  # Maximum requests per time window
TIME_WINDOW = 3600   # Time window in seconds (1 hour)

def get_real_ip(request: gr.Request):
    """Extract real IP address using x-forwarded-for header or fallback"""
    if not request:
        return "unknown"
    
    forwarded = request.headers.get("x-forwarded-for")
    if forwarded:
        ip = forwarded.split(",")[0].strip()  # First IP in the list is the client's
    else:
        ip = request.client.host  # fallback
    return ip

def check_rate_limit(request: gr.Request):
    """Check if the current request exceeds rate limits"""
    if not request:
        return True, "Rate limit check failed - no request info"
    
    ip = get_real_ip(request)
    now = time.time()

    # Clean up old timestamps outside the time window
    request_times[ip] = [t for t in request_times[ip] if now - t < TIME_WINDOW]
    

    # Check if rate limit exceeded
    if len(request_times[ip]) >= MAX_REQUESTS:
        time_remaining = int(TIME_WINDOW - (now - request_times[ip][0]))
        time_remaining_minutes = round(time_remaining / 60, 1)      
        time_window_minutes = round(TIME_WINDOW / 60, 1)
        
        return False, f"Rate limit exceeded. You can make {MAX_REQUESTS} requests per {time_window_minutes} minutes. Try again in {time_remaining_minutes} minutes."
    
    # Add current request timestamp
    request_times[ip].append(now)
    return True, ""

# ----------------- JigsawStack API Wrasppers ------------------

def summarize(text, url, file_store_key, summary_type, max_points, max_characters, request: gr.Request):
    """Summarize text, URL, or file store key using JigsawStack Summary API"""
    
    # Check rate limit first
    rate_limit_ok, rate_limit_msg = check_rate_limit(request)
    if not rate_limit_ok:
        return rate_limit_msg
    
    # Validate that at least one input method is provided
    if not text and not url and not file_store_key:
        return "Error: Please provide either text, URL, or file store key"
    
    payload = {}
    
    # Add text if provided
    if text and text.strip():
        payload["text"] = text.strip()
    
    # Add URL if provided
    if url and url.strip():
        payload["url"] = url.strip()
    
    # Add file store key if provided
    if file_store_key and file_store_key.strip():
        payload["file_store_key"] = file_store_key.strip()
    
    # Add summary type
    if summary_type:
        payload["type"] = summary_type
    
    # Add max points if provided and type is points
    if summary_type == "points" and max_points:
        try:
            max_points_int = int(max_points)
            if 1 <= max_points_int <= 100:
                payload["max_points"] = max_points_int
            else:
                return "Error: max_points must be between 1 and 100"
        except ValueError:
            return "Error: max_points must be a valid number"
    
    # Add max characters if provided
    if max_characters:
        try:
            max_chars_int = int(max_characters)
            if max_chars_int > 0:
                payload["max_characters"] = max_chars_int
            else:
                return "Error: max_characters must be greater than 0"
        except ValueError:
            return "Error: max_characters must be a valid number"
    
    try:
        r = requests.post(f"{BASE_URL}/ai/summary", headers=headers, json=payload)
        r.raise_for_status()
        result = r.json()
        
        if not result.get("success"):
            return f"Error: {result.get('message', 'Unknown error')}"
        
        summary = result.get("summary", "")
        
        # Format the output based on type
        if summary_type == "points" and isinstance(summary, list):
            return "\n• " + "\n• ".join(summary)
        else:
            return summary
            
    except requests.exceptions.RequestException as req_err:
        return f"Request failed: {str(req_err)}"
    except Exception as e:
        return f"Unexpected error: {str(e)}"

# ----------------- Gradio UI ------------------

with gr.Blocks() as demo:
    gr.Markdown("""
    <div style='text-align: center; margin-bottom: 24px;'>
        <h1 style='font-size:2.2em; margin-bottom: 0.2em;'>🧩 Text Summarizer</h1>
        <p style='font-size:1.2em; margin-top: 0;'>Generate concise, intelligent summaries of text, URLs, or stored documents with multiple output formats.</p>
        <p style='font-size:1em; margin-top: 0.5em;'>For more details and API usage, see the <a href='https://jigsawstack.com/docs/api-reference/ai/summary' target='_blank'>documentation</a>.</p>
    </div>
    """)

    with gr.Row():
        with gr.Column():
            gr.Markdown("#### Input Method (select one)")
            input_method = gr.Radio(
                choices=["Text", "URL", "File Store Key"],
                label="Choose Input Method",
                value="Text"
            )
            # Conditional inputs based on selection
            long_text = gr.Textbox(label="Text to Summarize", lines=8, placeholder="Enter your text here...")
            url_input = gr.Textbox(label="Document URL (PDF)", placeholder="https://example.com/document.pdf", visible=False)
            file_key = gr.Textbox(label="File Store Key", placeholder="your-file-store-key", visible=False)

        with gr.Column():
            gr.Markdown("#### Summary Options")
            summary_type = gr.Radio(
                choices=["text", "points"],
                label="Summary Format",
                value="text",
                info="Text: continuous paragraph | Points: bullet points"
            )
            max_points = gr.Slider(
                label="Max Points (2-100)",
                value=5,
                minimum=2,
                maximum=100,
                step=1,
                info="Only applies when format is 'points'"
            )
            max_characters = gr.Number(
                label="Max Characters",
                value=200,
                minimum=1,
                info="Optional: limit summary length"
            )

    summary_btn = gr.Button("Generate Summary", variant="primary")
    summary = gr.Textbox(label="Generated Summary", lines=10)

    # Function to show/hide input groups based on selection
    def update_input_visibility(method):
        if method == "Text":
            return gr.Textbox(visible=True), gr.Textbox(visible=False), gr.Textbox(visible=False)
        elif method == "URL":
            return gr.Textbox(visible=False), gr.Textbox(visible=True), gr.Textbox(visible=False)
        elif method == "File Store Key":
            return gr.Textbox(visible=False), gr.Textbox(visible=False), gr.Textbox(visible=True)
        else:
            return gr.Textbox(visible=True), gr.Textbox(visible=False), gr.Textbox(visible=False)

    input_method.change(
        update_input_visibility,
        inputs=input_method,
        outputs=[long_text, url_input, file_key]
    )

    summary_btn.click(
        summarize,
        inputs=[long_text, url_input, file_key, summary_type, max_points, max_characters],
        outputs=summary
    )

demo.launch()