import json import gradio as gr from openai import OpenAI from collect import fetch_reviews from triage import route_review, triage_review from rag import init_store, add_bug, search_bugs, clear_store init_store() def collect_and_triage(review, api_key): review_text = review["text"] route_data = route_review(review_text, api_key) route = route_data.get("route", "bug_report") if route != "bug_report": return None, route similar = search_bugs(review_text, top_k=2) structured = triage_review(review_text, api_key, similar_bugs=similar) add_bug(structured) return structured.get("title", ""), route def handle_collect(app_name, max_reviews, api_key_input): api_key = (api_key_input or "").strip() if not api_key: yield "OpenAI API key is required for BYOK." return yield f"Fetching reviews for {app_name}..." reviews = fetch_reviews(app_name, max_reviews=int(max_reviews)) yield f"Got {len(reviews)} reviews. Triaging..." titles = [] skipped = {"feature_request": 0, "general_complaint": 0} for review in reviews: title, route = collect_and_triage(review, api_key) if route == "bug_report" and title: titles.append(title) elif route in skipped: skipped[route] += 1 output = "\n".join([f"{i+1}. {t}" for i, t in enumerate(titles)]) yield ( f"Done — {len(titles)} bugs saved. " f"Skipped: {skipped['feature_request']} feature request(s), " f"{skipped['general_complaint']} general complaint(s).\n\n{output}" ) def build_triage_output(review_text,api_key): route_data = route_review(review_text, api_key) route = route_data.get("route", "bug_report") if route != "bug_report": confidence = route_data.get("confidence", 0) output = ( f"Route: {route} (confidence: {confidence})\n\n" "This input is not a bug report, so it was not added to bug store." ) return output, None similar = search_bugs(review_text, top_k=2) structured = triage_review(review_text, api_key, similar_bugs=similar) add_bug(structured) output = f"Severity: {structured.get('severity','')} | Component: {structured.get('component','')}\n\n" output += f"Bug report:\n```json\n{json.dumps(structured, indent=2)}\n```\n\n" output += "Similar bugs:\n" output += "\n".join([f"- {b.get('title','')} [{b.get('severity','')}]" for b in similar]) return output, structured def handle_triage(review_text, api_key_input): api_key = (api_key_input or "").strip() if not api_key: yield "OpenAI API key is required for BYOK." return yield "Triaging review..." output, structured = build_triage_output(review_text, api_key) yield output if not structured: return client = OpenAI(api_key=api_key) stream = client.chat.completions.create( model="gpt-4o", max_tokens=200, stream=True, messages=[{ "role": "user", "content": f"Write a 3 sentence QA incident summary:\n{json.dumps(structured, indent=2)}" }] ) output += "\nAI Summary:\n\n" for chunk in stream: output += chunk.choices[0].delta.content or "" yield output def build_search_output(results, query): output = f"{len(results)} results for: {query}\n\n---\n" output += "\n\n---\n".join([ f"{r.get('title','')}\n" f"{r.get('severity','')} | {r.get('component','')} | {r.get('platform','')}\n" f"{r.get('description','')}" for r in results ]) return output def get_ai_summary(results, query, api_key): client = OpenAI(api_key=api_key) context = "\n".join([ f"- {r.get('title','')}: {r.get('description','')}" for r in results ]) resp = client.chat.completions.create( model="gpt-4o", max_tokens=150, messages=[{ "role": "user", "content": f"Query: {query}\nBugs:\n{context}\nSummarise in 2 sentences:" }] ) return resp.choices[0].message.content def handle_search(query, api_key_input): api_key = (api_key_input or "").strip() if not api_key: return "OpenAI API key is required for BYOK." results = search_bugs(query, top_k=5) output = build_search_output(results, query) output += f"\n\nAI Summary:\n{get_ai_summary(results, query, api_key)}" return output def handle_clear_bugs(): removed = clear_store() init_store() return f"Cleared {removed} bug(s)." with gr.Blocks(title="QA Bug Triage") as demo: gr.Markdown("# QA Bug Triage Pipeline\nA modern RAG workflow for turning messy app reviews into structured, searchable QA bug intelligence..") api_key_box = gr.Textbox( label="OpenAI API key (BYOK)", placeholder="sk-...", type="password", value="" ) with gr.Tabs(): with gr.TabItem("1. Collect"): app_name_box = gr.Textbox(label="App name", value="notion") max_box = gr.Slider(5, 20, value=10, step=5, label="Max reviews") collect_btn = gr.Button("Fetch and triage", variant="primary") collect_out = gr.Markdown() collect_btn.click( handle_collect, [app_name_box, max_box, api_key_box], collect_out ) with gr.TabItem("2. Triage"): review_box = gr.Textbox( label="Paste a review", lines=4, placeholder="App crashes every time I try to upload a photo..." ) triage_btn = gr.Button("Triage", variant="primary") triage_out = gr.Markdown() triage_btn.click( handle_triage, [review_box, api_key_box], triage_out ) with gr.TabItem("3. Search"): search_box = gr.Textbox( label="Search query", placeholder="login crash android" ) search_btn = gr.Button("Search", variant="primary") search_out = gr.Markdown() search_btn.click( handle_search, [search_box, api_key_box], search_out ) with gr.TabItem("4. Clear bugs"): clear_btn = gr.Button("Clear stored bugs", variant="stop") clear_out = gr.Markdown() clear_btn.click( handle_clear_bugs, outputs=clear_out ) if __name__ == "__main__": demo.launch()