346 ریڈنگز
346 ریڈنگز

کس طرح AI Confluence & Streamlit کے ساتھ Agile پروگرام کے انتظام کو انقلابی بنا رہا ہے

کی طرف سے Ravikant Singh23m2025/06/14
Read on Terminal Reader

بہت لمبا؛ پڑھنے کے لئے

AI ایجنٹ Confluence سے منصوبوں کے اعداد و شمار کو جمع کرتا ہے اور جمع کرتا ہے، چیٹ کی طرف سے چلانے والی رپورٹوں، گرافوں اور نقطہ نظر کے ساتھ ایجائلی پروگرام کے انتظام کو آسان بناتا ہے.
featured image - کس طرح AI Confluence & Streamlit کے ساتھ Agile پروگرام کے انتظام کو انقلابی بنا رہا ہے
Ravikant Singh HackerNoon profile picture
0-item
1-item
2-item

Problem Statement:

مسئلہ کا بیان:

کمپنی کے ایپلی کیشنز میں Copilot کے ساتھ، فائلوں، SharePoint، اور دیگر دستیاب ذرائع سے کم از کم استعمال شدہ ڈیٹا تلاش کرنا بہت آسان ہو گیا ہے. میں اس Gen AI کی صلاحیت پر بھاری طور پر اعتماد کر رہا ہوں. ایک دن، مجھے تمام خصوصیات (Agile Framework میں ایک چارٹر کے لئے ٹیم کی فراہم کردہ) اور ان کی حیثیتوں کی ایک مجموعی نقطہ نظر کی ضرورت تھی جس پر ٹیم کام کر رہی ہے. بدقسمتی سے، Copilot نے Confluence صفحے سے ڈیٹا پڑھنے سے انکار کیا، جو مثالی ہے اور توقع کی جاتی ہے. بہت سے تنظیموں، پروجیکٹ، اور پروگرام اپ ڈیٹ Confluence صفحات پر ذخیرہ کیے جاتے ہیں. ٹیم کے مقاصد، فراہم کردہ، پروجیکٹ کے خطرے اور حیثیت کی ایک مجموعی نقطہ نظر حاصل کرنا ایک


Solution: AI Agentic Assistant Powered by Streamlit + Semantic Kernel

حل: AI ایجنٹک مددگار Streamlit + Semantic Kernel کی طرف سے Powered

ایجنٹک AI کا داخلہ میرے لئے ایک نجات دہندہ تھا، اور میں نے اس فریم ورک کو حل کے طور پر منتخب کرنے کا فیصلہ کیا. تاہم، چیلنجوں کا سامنا کرنا پڑا: کس فریم ورک کو استعمال کیا جانا چاہئے، اور کیا دستیاب ایک کھلی سافٹ ویئر ہے؟ کتنا مہنگی ہو جائے گا انتظام شدہ پلیٹ فارم؟ آخر میں، تمام تحقیق کے ساتھ، میں نے کھلی سافٹ ویئر کے ساتھ جانے کا فیصلہ کیا اور ایک ہلکا AI مددگار کی تعمیر کرنے کے لئے مندرجہ ذیل ٹیکنالوجی کا استعمال کرتے ہوئے:

  • آگے بڑھنے کی وجہ سے،
  • فوری انتظام اور زنجیرنگ کے لئے سیمنٹک کورل،
  • قدرتی زبان کی پروسیسنگ کے لئے Azure OpenAI
  • Confluence صفحات کے محفوظ اور متحرک ویب سکریپنگ کے لئے پلیئر.

کیا کرتا ہے

یہ آلہ پروگرام مینجمنٹ اور رہنماؤں کو اجازت دے گا:

  • Select a team or program name from a dropdown,

  • Automatically fetch the associated Confluence page URL,

  • Scrape key content sections from that page (like Features, Epics, Dependencies, Risks, Team Members),

  • Ask questions like “What are the team Q4 deliverables?” or “Summarize the features based on status,” etc.,

  • Display answers as summarized text.


How it works - Pseudo Code

یہ کیسے کام کرتا ہے - Pseudo Code

Step 1. Confluence Page LookUp.

URLs کو دستی طور پر لاگ ان کرنے کے بجائے، ہر ٹیم کا نام اپنے Confluence URL کو ایک کلمہ کا استعمال کرتے ہوئے متعارف کرایا جاتا ہے. جب ایک صارف دائیں پینل سے "Team A" کا انتخاب کرتا ہے تو، بیکنڈ خود کار طریقے سے متعلقہ URL کو تلاش کرتا ہے اور سکریپنگ کو روکتا ہے.


team_to_url_map = { "ٹیم A": "https://confluence.company.com/display/TEAM_A"،

"B ٹیم": "https://confluence.company.com/display/TEAM_B"، ... }

team_to_url_map = { "Team A": "https://confluence.company.com/display/TEAM_A",

"Team B": "https://confluence.company.com/display/TEAM_B", ... }


Step 2. Web Scraping via Playwright

آخر میں، میں نے سر کے بغیر براؤزر پر مبنی سکرپنگ کے لئے Playwright کا استعمال کرتے ہوئے ختم کیا، جو ہمیں ڈینمک مواد لوڈ کرنے اور لاگ ان کا انتظام کرنے میں مدد ملتی ہے:

ناکامی کا طریقہ:

  • [ ]Python درخواست لائبریری کا استعمال کرتے ہوئے، API کا استعمال کرتے ہوئے Confluence ڈیٹا حاصل کریں۔ تصدیق کے میکانیزم کامیاب نہیں تھے۔ دوسری صورت میں، یہ Confluence صفحے کے ڈیٹا حاصل کرنے کا ایک اچھا طریقہ ہوگا۔
  • [ ]Python BeautifySoup لائبریری کا استعمال کرتے ہوئے. یہ متحرک مواد کی وجہ سے خارج کیا گیا تھا.
  • [ ]میں پائٹون Playwright کے ساتھ ختم ہوا۔ SSO layer کو مسائل تھے، لیکن آخر میں، یہ HTML ریاست JSON ڈاؤن لوڈ کرنے اور اسے دوبارہ استعمال کرنے کے بعد کام کیا.


@kernel_function(description="Scrape and return text content from a Confluence page.") async def get_confluence_page_content(self, team_name: Annotated[str, "Name of the Agile team"]) -> Annotated[str, "Returns extracted text content from the page"):

@kernel_function(description="Scrape and return text content from a Confluence page.") async def get_confluence_page_content(self, team_name: Annotated[str, "Name of the Agile team"]) -> Annotated[str, "Returns extracted text content from the page"]:


Step 3. Define Client, Agent, and Prompt Engineering with Semantic Kernel.

اس آلے کو ایک پروگرام کے انتظام کے قابل بنانا چاہئے. ایجنٹ کی ہدایات اسکرپٹ مواد کو استعمال کرنے اور PM سوالات کے لئے مناسب نتیجہ پیدا کرنے کے لئے تیار کیے جاتے ہیں. ہدایات کو ایک ٹیکسٹ خلاصہ کے طور پر یا ایک گرافٹ فارمیٹ میں پیدا کرنے میں مدد ملے گی. یہ بھی کم کوڈ کا ایک مثال ہے.

اس کے علاوہ، میں نے کلائنٹ کو ایک AI ایجنٹ کے طور پر پلگ ان کے طور پر مقرر کیا.

AGENT_INSTRUCTIONS = “”

’’‘‘

کلائنٹ = OpenAI(<Local open source LLM>)

chat_completion_service = OpenAIChatCompletion(ai_model_id="<>",

async_client = کلائنٹ )

Agent = ChatCompletionAgent( service=chat_completion_service، پلگ ان=[ ConfluencePlugin() ]، نام="ConfluenceAgent", ہدایات=AGENT_INSTRUCTIONS )

AGENT_INSTRUCTIONS = “““

“““

client = OpenAI(<Local open source LLM>)

chat_completion_service = OpenAIChatCompletion(ai_model_id="<>",

async_client=client )

agent = ChatCompletionAgent( service=chat_completion_service, plugins=[ ConfluencePlugin() ], name="ConfluenceAgent", instructions=AGENT_INSTRUCTIONS )


Step 4. Decide on User Input to process the question with or without the tool.

میں نے یہ چیک کرنے کے لئے ایک اضافی LLM کلائنٹ شامل کرنے کا فیصلہ کیا ہے کہ کیا صارف کے انٹرویو پروگرامنگ کے لئے متعلقہ ہے یا نہیں.

(model="gpt-4o", messages=[ {"role": "system", "content": "آپ صارف input کے مواد کا فیصلہ کر رہے ہیں. Anlyze صارف input. If it asks to scrap internal COnfluence Page for a team then it is related to Program Management. If it is not related to Program Management, provide the answer but add 'Falseḳ' to the answer. If it is related to Program Management, add 'Trueḳ' to the response."}, {"role": "user", "content": user_input} ], temperature=0.5 )

completion = await client.chat.completions.create( model="gpt-4o", messages=[ {"role": "system", "content": "You are a Judge of the content of user input. Anlyze the user's input. If it asking to scrap internal COnfluence Page for a team then it is related to Program Management. If it is not related to Program Management, provide the reply but add 'False|' to the response. If it is related to Program Management, add 'True|' to the response."}, {"role": "user", "content": user_input} ], temperature=0.5 )


Step 5. The final step is to produce the result. Here is the entire code.

یہاں کل کوڈ ہے. میں نے اپنے پروجیکٹ مخصوص تفصیلات کو ہٹا دیا. ہم کو اس کو کوڈ میں استعمال کرنے کے لئے پہلے state.json کو ذخیرہ کرنے کی ضرورت ہے



زمرہ:تصویر سے زمرہ:تصویر سے زمرہ:تصویر سے زمرہ:تصویر سے زمرہ:تصویر سے زمرہ:تصویر سے زمرہ:تصویر سے زمرہ:تصویر سے زمرہ:تصویر سے زمرہ:تصویر سے زمرہ:تصویر سے زمرہ:تصویر سے زمرہ:تصویر سے زمرہ:تصویر سے زمرہ:تصویر سے زمرہ:تصویر سے زمرہ:تصویر سے زمرہ:تصویر سے زمرہ:تصویر سے زمرہ:تصویر سے زمرہ:تصویر سے زمرہ:تصویر سے زمرہ:تصویر سے زمرہ:تصویر سے زمرہ:تصو

Team_URL_MAPPING = { "Team 1": "Confluence URL for Team 1", "Team 2": "Confluence URL for Team 2", "Team 3": "Confluence URL for Team 3", "Team 4": "Confluence URL for Team 4", "Team 5": "Confluence URL for Team 5", "Team 6": "Confluence URL for Team 6" }

# ---- پلگ ان کی تعریف ----

#Bar chart with fixed size def plot_bar_chart(df): status_counts = df["status"].value_counts() fig, ax = plt.subplots(figsize=(1.5, 1)) # width, height in in inches ax.bar(status_counts.index, status_counts.values, color="#4CAF50") ax.set_title("Features by Status") ax.set_ylabel("Count") # change tick color ax.tick_params(axis='x', colors='blue', labelrotation=90) # x-ticks in blue, rotated axtick_params(axis='y', colors='green') # y-ticks in green st.pyplot(fig)

def extract_json_from_response(text): # regex کا استعمال کرتے ہوئے ٹیسٹ میچ میں پہلا JSON array تلاش کرنے کے لئے = re.search(r"(\[\s*{.*\s*\])", text, re.DOTALL) اگر match: return match.group(1) return کوئی

فہرست ConfluencePlugin: def init(self): self.default_confluence_url = "<>" load_dotenv()

@kernel_function(description="Scrape and return text content from a Confluence page.")
async def get_confluence_page_content(
            self, team_name: Annotated\[str, "Name of the Agile team"\]
            ) -> Annotated\[str, "Returns extracted text content from the page"\]:
            print(f"Attempting to scrape Confluence page for team: '{team_name}'") # Added for debugging
            target_url = TEAM_URL_MAPPING.get(team_name)
            if not target_url:
                print(f"Failed to find URL for team: '{team_name}' in TEAM_URL_MAPPING.") # Added for debugging
                return f"❌ No Confluence URL mapped for team '{team_name}'"

            async with async_playwright() as p:
                browser = await p.chromium.launch()
                context = await browser.new_context(storage_state="state.json")
                page = await context.new_page()                    
                pages_to_scrape = \[target_url\]

                # Loop through each page URL and scrape the content
                for page_url in pages_to_scrape:
                    await page.goto(page_url)
                    await asyncio.sleep(30)  # Wait for the page to load
                    await page.wait_for_selector('div.refresh-module-id, table.some-jira-table')
                    html = await page.content()
                    soup = BeautifulSoup(html, "html.parser")
                    body_div = soup.find("div", class_="wiki-content") or soup.body
                    if not body_div:
                        return "❌ Could not find content on the Confluence page."
                    # Process the scraped content (example: extract headings)
                    headings = soup.find_all('h2')
                    text = body_div.get_text(separator="\\n", strip=True)
                    return text\[:4000\]  # Truncate if needed to stay within token limits
                await browser.close()               
@kernel_function(description="Summarize and structure scraped Confluence content into JSON.")
async def summarize_confluence_data(
    self, raw_text: Annotated\[str, "Raw text scraped from the Confluence page"\],
    output_style: Annotated\[str, "Output style, either 'bullet' or 'json'"\] = "json"  # Default to 'json'
) -> Annotated\[str, "Returns structured summary in JSON format"\]:

    prompt = f"""
    You are a Program Management Data Extractor.
    Your job is to analyze the following Confluence content and produce structured machine-readable output.

    Confluence Content:
    {raw_text}

    Instructions:
    - If output_style is 'bullet', return bullet points summary.
    - If output_style is 'json', return only valid JSON array by removing un printable characters and spaces from beginning and end.
    - DO NOT write explanations.
    - DO NOT suggest code snippets.
    - DO NOT wrap JSON inside triple backticks \`\`\`json
    - Output ONLY the pure JSON array or bullet points list.

    Output_style: {output_style}
        """

    # Call OpenAI again
    completion = await client.chat.completions.create(
        model="gpt-4o",
        messages=\[
            {"role": "system", "content": "You are a helpful Program Management Data Extractor."},
            {"role": "user", "content": prompt}
        \],
        temperature=0.1
        )

    structured_json = completion.choices\[0\].message.content.strip()
    return structured_json

# ---- لوڈ API سرٹیفکیٹ ---- load_dotenv() کلائنٹ = AsyncAzureOpenAI( azure_endpoint="<>", api_key=os.getenv("AZURE_API_KEY"), api_version='<>' ) chat_completion_service = OpenAIChatCompletion( ai_model_id="<>", async_client=client )

AGENT_INSTRUCTIONS = """آپ ایک مفید پروگرامنگ AI ایجنٹ ہیں جو ایک مجموعہ صفحہ سے اہم معلومات کو نکالنے میں مدد کرسکتے ہیں جیسے ٹیم کے رکن، خصوصیات، Epics.

اہم: جب صارفین کو ایک ٹیم صفحے کی وضاحت کرتے ہیں تو، صرف اس ٹیم کے خصوصیات اور Epics نکالیں.

جب بات چیت شروع ہوتی ہے تو، اپنے آپ کو اس پیغام کے ساتھ پیش کریں: "ہاں! میں آپ کے PM مددگار ہوں. میں آپ کو خصوصیات اور Epics کی حیثیت حاصل کرنے میں مدد کر سکتا ہوں.

مرحلے آپ کی پیروی کرنا چاہئے: 1. سب سے پہلے سب سے پہلے کلک کریں `get_confluence_page_content` Confluence صفحے کو سکریپ کرنے کے لئے.

  • اگر صارف کا پیغام "Team: {team_name}." سے شروع ہوتا ہے، تو اس {team_name} کا استعمال کریں ۔ مثال کے طور پر اگر داخلہ "Team: Raptor" ہے تو کیا تازہ ترین خصوصیات ہیں؟ "team_name" "Raptor" ہے ۔ 2۔ اگر صارف نے خلاصہ کی درخواست کی ہے تو ایک گولٹ پوائنٹس کی فہرست فراہم کریں ۔ 3۔ اگر صارف نے ایک JSON array یا chart یا plot کی درخواست کی ہے تو پھر فوری طور پر استعمال کردہ مواد کا استعمال کرتے ہوئے 'summarize_confluence_data' کال کریں ۔ 4۔ صارف کی درخواست کردہ پیداوار سٹائل پر مبنی طور پر ایک JSON array یا گولٹ پوائنٹس کو واپس کریں ۔ 5۔ اگر صارف نے ایک پیداوار سٹائل کو مقرر نہیں کیا ہے تو، گولٹ پوائنٹس کے خلاصے کو

ہدایات: - اگر output_style 'bullet' ہے تو، بلٹ پوائنٹس خلاصہ کی واپسی کریں. - اگر output_style 'json' ہے تو، صرف درست JSON ماریہ کو شروع اور ختم سے un پرنٹ کرنے کے قابل حروف اور فاصلے کو ہٹانے سے واپسی کریں. - وضاحتیں نہیں لکھیں. - کوڈ ٹکڑے کی تجویز نہ کریں. - ٹریپل backticks کے اندر JSON کو نہیں پکڑیں ```json - صرف خالص JSON ماریہ یا بلٹ پوائنٹس کی فہرست کو نکالیں.

کیا ٹیم آپ کو آج آپ کی منصوبہ بندی میں مدد کرنے کے لئے دلچسپی رکھتے ہیں؟ "

ہمیشہ صارف کی ترجیحات کو ترجیح دیں. اگر وہ ایک مخصوص ٹیم کا ذکر کرتے ہیں تو، آپ کے اعداد و شمار کو اس ٹیم پر توجہ مرکوز کریں اور متبادلوں کی سفارش کرنے کے بجائے. """ ایجنٹ = ChatCompletionAgent( service=chat_completion_service، پلگ ان=[ ConfluencePlugin() ]، name="ConfluenceAgent", instructions=AGENT_INSTRUCTIONS )



# ---- Main async logic ---- async def stream_response(user_input, thread=None): html_blocks = [] full_response = [] function_calls = [] parsed_json_result = None completion = await client.chat.completions.create( model="gpt-4o", messages=["role": "systems", "content": "آپ صارف input کے مواد کا فیصلہ کرتے ہیں. Anlyze صارف input. If it asks to scrap internal COnfluence Page for a team then it is related to Program Management. If it is not related to Program Management, provide the answer but add 'False Tilt' to the response. If it is related to Program Management, add 'True Tilt' to the response."}, {role user function": "

    async for response in agent.invoke_stream(messages=user_input, thread=thread):
        print("Response:", response)
        thread = response.thread
        agent_name = response.name
        for item in list(response.items):
            if isinstance(item, FunctionCallContent):
                pass  # You can ignore this now               
            elif isinstance(item, FunctionResultContent):
                if item.name == "summarize_confluence_data":
                    raw_content = item.result
                    extracted_json = extract_json_from_response(raw_content)

                    if extracted_json:
                        try:
                            parsed_json = json.loads(extracted_json)
                            yield parsed_json, thread, function_calls
                        except Exception as e:
                            st.error(f"Failed to parse extracted JSON: {e}")
                    else:
                        full_response.append(raw_content)
                else:
                    full_response.append(item.result)

            elif isinstance(item, StreamingTextContent) and item.text:
                full_response.append(item.text)
                #print("Full Response:", full_response)

    # After loop ends, yield final result
    if parsed_json_result:
        yield parsed_json_result, thread, function_calls
    else:
        yield ''.join(full_response), thread, function_calls

# ---- Streamlit UI Setup ---- st.set_page_config(layout="wide") left_col, right_col = st.columns([1, 1]) st.markdown("" <style> html, جسم, [class*="css"] { font-size: 12px !important; } </style> """, unsafe_allow_html=True) # ---- Main Streamlit app ---- with left_col: st.title(" Program Management Enabler AI") st.write("مجھے مختلف Wiley پروگرام کے متعلق پوچھیں!") st.write("میں آپ کو خصوصیات اور Epics کی حیثیت حاصل کرنے میں مدد کر سکتا ہوں.")

if "history" not in st.session_state:
    st.session_state.history = \[\]

if "thread" not in st.session_state:
    st.session_state.thread = None

if "charts" not in st.session_state:
    st.session_state.charts = \[\]  # Each entry: {"df": ..., "title": ..., "question": ...}

if "chart_dataframes" not in st.session_state:
    st.session_state.chart_dataframes = \[\]

if st.button("🧹 Clear Chat"):
    st.session_state.history = \[\]
    st.session_state.thread = None
    st.rerun()

# Input box at the top
user_input = st.chat_input("Ask me about your team's features...")
# Example:
team_selected = st.session_state.get("selected_team")
if st.session_state.get("selected_team") and user_input:
    user_input = f"Team: {st.session_state.get('selected_team')}. {user_input}"
    # Preserve chat history when program or team is selected
    if user_input and not st.session_state.get("selected_team_changed", False):
        st.session_state.selected_team_changed = False
if user_input:
    df = pd.DataFrame()
    full_response_holder = {"text": "","df": None}

    with st.chat_message("assistant"):
        response_container = st.empty()
        assistant_text = ""            
        try:
            chat_index = len(st.session_state.history)
            response_gen = stream_response(user_input, st.session_state.thread)
            print("Response generator started",response_gen)
            async def process_stream():
                async for update in response_gen:
                    nonlocal_thread = st.session_state.thread
                    if len(update) == 3:
                        content, nonlocal_thread, function_calls = update
                        full_response_holder\["text"\] = content
                        if isinstance(content, list):
                            data = json.loads(re.sub(r'\[\\x00-\\x1F\\x7F\]', '', content.replace("\`\`\`json", "").replace("\`\`\`","")))
                            df = pd.DataFrame(data)
                            df.columns = df.columns.str.lower()
                            print("\\n📊 Features Status Chart")                      
                            st.subheader("📊 Features Status Chart")
                            plot_bar_chart(df)                                
                            st.subheader("📋 Detailed Features Table")
                            st.dataframe(df)                                                             
                            chart_df.columns = chart_df.columns.str.lower()
                            full_response_holder\["df"\] = chart_df                                
                            
                        elif (re.sub(r'\[\\x00-\\x1F\\x7F\]', '', content.replace("\`\`\`json", "").replace("\`\`\`","").replace(" ",""))\[0\] =="\[" and re.sub(r'\[\\x00-\\x1F\\x7F\]', '', content.replace("\`\`\`json", "").replace("\`\`\`","").replace(" ",""))\[-1\] == "\]"):
                            data = json.loads(re.sub(r'\[\\x00-\\x1F\\x7F\]', '', content.replace("\`\`\`json", "").replace("\`\`\`","")))
                            df = pd.DataFrame(data)
                            df.columns = df.columns.str.lower()
                            chart_df = pd.DataFrame(data)
                            chart_df.columns = chart_df.columns.str.lower()
                            full_response_holder\["df"\] = chart_df
                        else:
                            if function_calls:
                                st.markdown("\\n".join(function_calls))
                            flagtext = 'text'

                    st.session_state.thread = nonlocal_thread

            try:
                with st.spinner("🤖 AI is thinking..."):
                    flagtext = None
                    # Run the async function to process the stream
                    asyncio.run(process_stream())
                      # Update history with the assistant's response
                    if full_response_holder\["df"\] is not None and flagtext is None:
                        st.session_state.chart_dataframes.append({
                            "question": user_input,
                            "data": full_response_holder\["df"\],
                            "type": "chart"
                        })
                    elif full_response_holder\["text"\].strip():
                        # Text-type response
                        st.session_state.history.append({
                            "user": user_input,
                            "assistant": full_response_holder\["text"\],
                            "type": "text"
                        })
                    flagtext = None

            except Exception as e:
                error_msg = f"⚠️ Error: {e}"
                response_container.markdown(error_msg)
                if chat_index > 0 and "Error" in full_response_holder\["text"\]:
                    # Remove the last message only if it was an error
                    st.session_state.history.pop(chat_index)
        # Handle any exceptions that occur during the async call
        except Exception as e:
            full_response_holder\["text"\] = f"⚠️ Error: {e}"
            response_container.markdown(full_response_holder\["text"\])
    chat_index = len(st.session_state.history)

#for item in st.session_state.history\[:-1\]:
for item in reversed(st.session_state.history):
    if item\["type"\] == "text":
        with st.chat_message("user"):
            st.markdown(item\["user"\])
        with st.chat_message("assistant"):
            st.markdown(item\["assistant"\])


with right_col:st.title("Wiley پروگرام منتخب کریں")

team_list = {
    "Program 1": \["Team 1", "Team 2", "Team 3"\],
    "Program 2": \["Team 4", "Team 5", "Team 6"\]
    }
selected_program = st.selectbox("Select the Program:", \["No selection"\] + list(team_list.keys()), key="program_selectbox")
selected_team = st.selectbox("Select the Agile Team:", \["No selection"\] + team_list.get(selected_program, \[\]), key="team_selectbox")
st.session_state\["selected_team"\] = selected_team if selected_team != "No selection" else None

if st.button("🧹 Clear All Charts"):
    st.session_state.chart_dataframes = \[\]

chart_idx = 1
#if len(st.session_state.chart_dataframes) == 1:
for idx, item in enumerate(st.session_state.chart_dataframes):
#for idx, item in enumerate(st.session_state.chart_dataframes):
    st.markdown(f"\*\*Chart {idx + 1}: {item\['question'\]}\*\*")
    st.subheader("📊 Features Status Chart")
    plot_bar_chart(item\["data"\])
    st.subheader("📋 Detailed Features Table")
    st.dataframe(item\["data"\])
    chart_idx += 1


زمرہ:تصویر سے زمرہ:تصویر سے زمرہ:تصویر سے زمرہ:تصویر سے زمرہ:تصویر سے زمرہ:تصویر سے زمرہ:تصویر سے زمرہ:تصویر سے زمرہ:تصویر سے زمرہ:تصویر سے زمرہ:تصویر سے زمرہ:تصویر سے زمرہ:تصویر سے زمرہ:تصویر سے زمرہ:تصویر سے زمرہ:تصویر سے زمرہ:تصویر سے زمرہ:تصویر سے زمرہ:تصویر سے زمرہ:تصویر سے زمرہ:تصویر سے زمرہ:تصویر سے زمرہ:تصویر سے زمرہ:تصویر سے زمرہ:تصو

Team_URL_MAPPING = { "Team 1": "Confluence URL for Team 1", "Team 2": "Confluence URL for Team 2", "Team 3": "Confluence URL for Team 3", "Team 4": "Confluence URL for Team 4", "Team 5": "Confluence URL for Team 5", "Team 6": "Confluence URL for Team 6" }

# ---- پلگ ان کی تعریف ----

#Bar chart with fixed size def plot_bar_chart(df): status_counts = df["status"].value_counts() fig, ax = plt.subplots(figsize=(1.5, 1)) # width, height in in inches ax.bar(status_counts.index, status_counts.values, color="#4CAF50") ax.set_title("Features by Status") ax.set_ylabel("Count") # change tick color ax.tick_params(axis='x', colors='blue', labelrotation=90) # x-ticks in blue, rotated axtick_params(axis='y', colors='green') # y-ticks in green st.pyplot(fig)

def extract_json_from_response(text): # regex کا استعمال کرتے ہوئے ٹیسٹ میچ میں پہلا JSON array تلاش کرنے کے لئے = re.search(r"(\[\s*{.*\s*\])", text, re.DOTALL) اگر match: return match.group(1) return کوئی

فہرست ConfluencePlugin: def init(self): self.default_confluence_url = "<>" load_dotenv()

@kernel_function(description="Scrape and return text content from a Confluence page.")
async def get_confluence_page_content(
            self, team_name: Annotated\[str, "Name of the Agile team"\]
            ) -> Annotated\[str, "Returns extracted text content from the page"\]:
            print(f"Attempting to scrape Confluence page for team: '{team_name}'") # Added for debugging
            target_url = TEAM_URL_MAPPING.get(team_name)
            if not target_url:
                print(f"Failed to find URL for team: '{team_name}' in TEAM_URL_MAPPING.") # Added for debugging
                return f"❌ No Confluence URL mapped for team '{team_name}'"

            async with async_playwright() as p:
                browser = await p.chromium.launch()
                context = await browser.new_context(storage_state="state.json")
                page = await context.new_page()                    
                pages_to_scrape = \[target_url\]

                # Loop through each page URL and scrape the content
                for page_url in pages_to_scrape:
                    await page.goto(page_url)
                    await asyncio.sleep(30)  # Wait for the page to load
                    await page.wait_for_selector('div.refresh-module-id, table.some-jira-table')
                    html = await page.content()
                    soup = BeautifulSoup(html, "html.parser")
                    body_div = soup.find("div", class_="wiki-content") or soup.body
                    if not body_div:
                        return "❌ Could not find content on the Confluence page."
                    # Process the scraped content (example: extract headings)
                    headings = soup.find_all('h2')
                    text = body_div.get_text(separator="\\n", strip=True)
                    return text\[:4000\]  # Truncate if needed to stay within token limits
                await browser.close()               
@kernel_function(description="Summarize and structure scraped Confluence content into JSON.")
async def summarize_confluence_data(
    self, raw_text: Annotated\[str, "Raw text scraped from the Confluence page"\],
    output_style: Annotated\[str, "Output style, either 'bullet' or 'json'"\] = "json"  # Default to 'json'
) -> Annotated\[str, "Returns structured summary in JSON format"\]:

    prompt = f"""
    You are a Program Management Data Extractor.
    Your job is to analyze the following Confluence content and produce structured machine-readable output.

    Confluence Content:
    {raw_text}

    Instructions:
    - If output_style is 'bullet', return bullet points summary.
    - If output_style is 'json', return only valid JSON array by removing un printable characters and spaces from beginning and end.
    - DO NOT write explanations.
    - DO NOT suggest code snippets.
    - DO NOT wrap JSON inside triple backticks \`\`\`json
    - Output ONLY the pure JSON array or bullet points list.

    Output_style: {output_style}
        """

    # Call OpenAI again
    completion = await client.chat.completions.create(
        model="gpt-4o",
        messages=\[
            {"role": "system", "content": "You are a helpful Program Management Data Extractor."},
            {"role": "user", "content": prompt}
        \],
        temperature=0.1
        )

    structured_json = completion.choices\[0\].message.content.strip()
    return structured_json

# ---- لوڈ API سرٹیفکیٹ ---- load_dotenv() کلائنٹ = AsyncAzureOpenAI( azure_endpoint="<>", api_key=os.getenv("AZURE_API_KEY"), api_version='<>' ) chat_completion_service = OpenAIChatCompletion( ai_model_id="<>", async_client=client )

AGENT_INSTRUCTIONS = """آپ ایک مفید پروگرامنگ AI ایجنٹ ہیں جو ایک مجموعہ صفحہ سے اہم معلومات کو نکالنے میں مدد کرسکتے ہیں جیسے ٹیم کے رکن، خصوصیات، Epics.

اہم: جب صارفین کو ایک ٹیم صفحے کی وضاحت کرتے ہیں تو، صرف اس ٹیم کے خصوصیات اور Epics نکالیں.

جب بات چیت شروع ہوتی ہے تو، اپنے آپ کو اس پیغام کے ساتھ پیش کریں: "ہاں! میں آپ کے PM مددگار ہوں. میں آپ کو خصوصیات اور Epics کی حیثیت حاصل کرنے میں مدد کر سکتا ہوں.

مرحلے آپ کی پیروی کرنا چاہئے: 1. سب سے پہلے سب سے پہلے کلک کریں `get_confluence_page_content` Confluence صفحے کو سکریپ کرنے کے لئے.

  • اگر صارف کا پیغام "Team: {team_name}." سے شروع ہوتا ہے، تو اس {team_name} کا استعمال کریں ۔ مثال کے طور پر اگر داخلہ "Team: Raptor" ہے تو کیا تازہ ترین خصوصیات ہیں؟ "team_name" "Raptor" ہے ۔ 2۔ اگر صارف نے خلاصہ کی درخواست کی ہے تو ایک گولٹ پوائنٹس کی فہرست فراہم کریں ۔ 3۔ اگر صارف نے ایک JSON array یا chart یا plot کی درخواست کی ہے تو پھر فوری طور پر استعمال کردہ مواد کا استعمال کرتے ہوئے 'summarize_confluence_data' کال کریں ۔ 4۔ صارف کی درخواست کردہ پیداوار سٹائل پر مبنی طور پر ایک JSON array یا گولٹ پوائنٹس کو واپس کریں ۔ 5۔ اگر صارف نے ایک پیداوار سٹائل کو مقرر نہیں کیا ہے تو، گولٹ پوائنٹس کے خلاصے کو

ہدایات: - اگر output_style 'bullet' ہے تو، بلٹ پوائنٹس خلاصہ کی واپسی کریں. - اگر output_style 'json' ہے تو، صرف درست JSON ماریہ کو شروع اور ختم سے un پرنٹ کرنے کے قابل حروف اور فاصلے کو ہٹانے سے واپسی کریں. - وضاحتیں نہیں لکھیں. - کوڈ ٹکڑے کی تجویز نہ کریں. - ٹریپل backticks کے اندر JSON کو نہیں پکڑیں ```json - صرف خالص JSON ماریہ یا بلٹ پوائنٹس کی فہرست کو نکالیں.

کیا ٹیم آپ کو آج آپ کی منصوبہ بندی میں مدد کرنے کے لئے دلچسپی رکھتے ہیں؟ "

ہمیشہ صارف کی ترجیحات کو ترجیح دیں. اگر وہ ایک مخصوص ٹیم کا ذکر کرتے ہیں تو، آپ کے اعداد و شمار کو اس ٹیم پر توجہ مرکوز کریں اور متبادلوں کی سفارش کرنے کے بجائے. """ ایجنٹ = ChatCompletionAgent( service=chat_completion_service، پلگ ان=[ ConfluencePlugin() ]، name="ConfluenceAgent", instructions=AGENT_INSTRUCTIONS )



# ---- Main async logic ---- async def stream_response(user_input, thread=None): html_blocks = [] full_response = [] function_calls = [] parsed_json_result = None completion = await client.chat.completions.create( model="gpt-4o", messages=["role": "systems", "content": "آپ صارف input کے مواد کا فیصلہ کرتے ہیں. Anlyze صارف input. If it asks to scrap internal COnfluence Page for a team then it is related to Program Management. If it is not related to Program Management, provide the answer but add 'False Tilt' to the response. If it is related to Program Management, add 'True Tilt' to the response."}, {role user function": "

    async for response in agent.invoke_stream(messages=user_input, thread=thread):
        print("Response:", response)
        thread = response.thread
        agent_name = response.name
        for item in list(response.items):
            if isinstance(item, FunctionCallContent):
                pass  # You can ignore this now               
            elif isinstance(item, FunctionResultContent):
                if item.name == "summarize_confluence_data":
                    raw_content = item.result
                    extracted_json = extract_json_from_response(raw_content)

                    if extracted_json:
                        try:
                            parsed_json = json.loads(extracted_json)
                            yield parsed_json, thread, function_calls
                        except Exception as e:
                            st.error(f"Failed to parse extracted JSON: {e}")
                    else:
                        full_response.append(raw_content)
                else:
                    full_response.append(item.result)

            elif isinstance(item, StreamingTextContent) and item.text:
                full_response.append(item.text)
                #print("Full Response:", full_response)

    # After loop ends, yield final result
    if parsed_json_result:
        yield parsed_json_result, thread, function_calls
    else:
        yield ''.join(full_response), thread, function_calls

# ---- Streamlit UI Setup ---- st.set_page_config(layout="wide") left_col, right_col = st.columns([1, 1]) st.markdown("" <style> html, جسم, [class*="css"] { font-size: 12px !important; } </style> """, unsafe_allow_html=True) # ---- Main Streamlit app ---- with left_col: st.title(" Program Management Enabler AI") st.write("مجھے مختلف Wiley پروگرام کے متعلق پوچھیں!") st.write("میں آپ کو خصوصیات اور Epics کی حیثیت حاصل کرنے میں مدد کر سکتا ہوں.")

if "history" not in st.session_state:
    st.session_state.history = \[\]

if "thread" not in st.session_state:
    st.session_state.thread = None

if "charts" not in st.session_state:
    st.session_state.charts = \[\]  # Each entry: {"df": ..., "title": ..., "question": ...}

if "chart_dataframes" not in st.session_state:
    st.session_state.chart_dataframes = \[\]

if st.button("🧹 Clear Chat"):
    st.session_state.history = \[\]
    st.session_state.thread = None
    st.rerun()

# Input box at the top
user_input = st.chat_input("Ask me about your team's features...")
# Example:
team_selected = st.session_state.get("selected_team")
if st.session_state.get("selected_team") and user_input:
    user_input = f"Team: {st.session_state.get('selected_team')}. {user_input}"
    # Preserve chat history when program or team is selected
    if user_input and not st.session_state.get("selected_team_changed", False):
        st.session_state.selected_team_changed = False
if user_input:
    df = pd.DataFrame()
    full_response_holder = {"text": "","df": None}

    with st.chat_message("assistant"):
        response_container = st.empty()
        assistant_text = ""            
        try:
            chat_index = len(st.session_state.history)
            response_gen = stream_response(user_input, st.session_state.thread)
            print("Response generator started",response_gen)
            async def process_stream():
                async for update in response_gen:
                    nonlocal_thread = st.session_state.thread
                    if len(update) == 3:
                        content, nonlocal_thread, function_calls = update
                        full_response_holder\["text"\] = content
                        if isinstance(content, list):
                            data = json.loads(re.sub(r'\[\\x00-\\x1F\\x7F\]', '', content.replace("\`\`\`json", "").replace("\`\`\`","")))
                            df = pd.DataFrame(data)
                            df.columns = df.columns.str.lower()
                            print("\\n📊 Features Status Chart")                      
                            st.subheader("📊 Features Status Chart")
                            plot_bar_chart(df)                                
                            st.subheader("📋 Detailed Features Table")
                            st.dataframe(df)                                                             
                            chart_df.columns = chart_df.columns.str.lower()
                            full_response_holder\["df"\] = chart_df                                
                            
                        elif (re.sub(r'\[\\x00-\\x1F\\x7F\]', '', content.replace("\`\`\`json", "").replace("\`\`\`","").replace(" ",""))\[0\] =="\[" and re.sub(r'\[\\x00-\\x1F\\x7F\]', '', content.replace("\`\`\`json", "").replace("\`\`\`","").replace(" ",""))\[-1\] == "\]"):
                            data = json.loads(re.sub(r'\[\\x00-\\x1F\\x7F\]', '', content.replace("\`\`\`json", "").replace("\`\`\`","")))
                            df = pd.DataFrame(data)
                            df.columns = df.columns.str.lower()
                            chart_df = pd.DataFrame(data)
                            chart_df.columns = chart_df.columns.str.lower()
                            full_response_holder\["df"\] = chart_df
                        else:
                            if function_calls:
                                st.markdown("\\n".join(function_calls))
                            flagtext = 'text'

                    st.session_state.thread = nonlocal_thread

            try:
                with st.spinner("🤖 AI is thinking..."):
                    flagtext = None
                    # Run the async function to process the stream
                    asyncio.run(process_stream())
                      # Update history with the assistant's response
                    if full_response_holder\["df"\] is not None and flagtext is None:
                        st.session_state.chart_dataframes.append({
                            "question": user_input,
                            "data": full_response_holder\["df"\],
                            "type": "chart"
                        })
                    elif full_response_holder\["text"\].strip():
                        # Text-type response
                        st.session_state.history.append({
                            "user": user_input,
                            "assistant": full_response_holder\["text"\],
                            "type": "text"
                        })
                    flagtext = None

            except Exception as e:
                error_msg = f"⚠️ Error: {e}"
                response_container.markdown(error_msg)
                if chat_index > 0 and "Error" in full_response_holder\["text"\]:
                    # Remove the last message only if it was an error
                    st.session_state.history.pop(chat_index)
        # Handle any exceptions that occur during the async call
        except Exception as e:
            full_response_holder\["text"\] = f"⚠️ Error: {e}"
            response_container.markdown(full_response_holder\["text"\])
    chat_index = len(st.session_state.history)

#for item in st.session_state.history\[:-1\]:
for item in reversed(st.session_state.history):
    if item\["type"\] == "text":
        with st.chat_message("user"):
            st.markdown(item\["user"\])
        with st.chat_message("assistant"):
            st.markdown(item\["assistant"\])


with right_col:st.title("Wiley پروگرام منتخب کریں")

team_list = {
    "Program 1": \["Team 1", "Team 2", "Team 3"\],
    "Program 2": \["Team 4", "Team 5", "Team 6"\]
    }
selected_program = st.selectbox("Select the Program:", \["No selection"\] + list(team_list.keys()), key="program_selectbox")
selected_team = st.selectbox("Select the Agile Team:", \["No selection"\] + team_list.get(selected_program, \[\]), key="team_selectbox")
st.session_state\["selected_team"\] = selected_team if selected_team != "No selection" else None

if st.button("🧹 Clear All Charts"):
    st.session_state.chart_dataframes = \[\]

chart_idx = 1
#if len(st.session_state.chart_dataframes) == 1:
for idx, item in enumerate(st.session_state.chart_dataframes):
#for idx, item in enumerate(st.session_state.chart_dataframes):
    st.markdown(f"\*\*Chart {idx + 1}: {item\['question'\]}\*\*")
    st.subheader("📊 Features Status Chart")
    plot_bar_chart(item\["data"\])
    st.subheader("📋 Detailed Features Table")
    st.dataframe(item\["data"\])
    chart_idx += 1


نتیجہ

کےStreamlit-based Program Management AI chatbotٹیموں کو Confluence صفحات سے منصوبے کی خصوصیات اور epics کی پیروی کرنے میں مدد ملتی ہے.Semantic Kernel agentsOpenAI GPT-4o کے ساتھ Team-specific Confluence صفحے کے مواد کا استعمال کرنے کے لئےPlaywrightریاست کی تصدیق کے لئے استعمال کیا جاتا ہے. آلہ پروگرام اور متعلقہ ٹیم کا انتخاب کرنے کی اجازت دیتا ہے، اور انتخاب پر مبنی، صارف کے انٹرویو کا جواب دیا جائے گا. ایجنٹک AI خصوصیت کے ساتھ، ہم LLM کو ایک حقیقی ذاتی مددگار بننے کی اجازت دے سکتے ہیں. یہ ڈیٹا کے لئے LLM کی رسائی کو محدود کرنے میں طاقتور ہوسکتا ہے، لیکن اب بھی محدود ڈیٹا کے LLM خصوصیت کا استعمال کرتے ہیں. یہ ایجنٹک AI خصوصیت کو سمجھنے کے لئے ایک مثال ہے اور یہ کتنا طاقتور ہوسکتا ہے.


ملاحظہ کریں:https://github.com/microsoft/ai-agents-for-beginners?tab=readme-ov-file

              Playwright documentation. 


Trending Topics

blockchaincryptocurrencyhackernoon-top-storyprogrammingsoftware-developmenttechnologystartuphackernoon-booksBitcoinbooks