338 lexime
338 lexime

Si AI po revolucionarizon menaxhimin e programit Agile me Confluence & Streamlit

nga Ravikant Singh23m2025/06/14
Read on Terminal Reader

Shume gjate; Te lexosh

Agjenti i inteligjencës artificiale mbledh dhe përmbledh të dhënat e projektit nga Confluence, duke përmirësuar menaxhimin e programit Agile me raporte, grafikë dhe njohuri të drejtuara nga chat.
featured image - Si AI po revolucionarizon menaxhimin e programit Agile me Confluence & Streamlit
Ravikant Singh HackerNoon profile picture
0-item
1-item
2-item

Problem Statement:

Problemi i deklaratës:

Me Copilot të integruar në aplikacionet e organizatës, gjetja e të dhënave të rralla të përdorura nga skedarët, SharePoint dhe burime të tjera të arritshme është bërë jashtëzakonisht e lehtë. Unë kam qenë duke u mbështetur shumë në këtë aftësi të gjenit AI. Një ditë, kam pasur nevojë për një pamje të përgjithshme të të gjitha karakteristikave (dërgesat e ekipit për një çerek në Agile Framework) dhe statuset e tyre në të cilat ekipi po punon. Për fat të keq, Copilot mohoi leximin e të dhënave nga faqja e Confluence, e cila është ideale dhe e pritshme. Shumë organizata, projekte dhe përditësime të programeve ruhen në faqet e Confluence. Marrja e një pamje të përgjithshme të qëllimeve të ekip


Solution: AI Agentic Assistant Powered by Streamlit + Semantic Kernel

Solution: AI Agentic Assistant Powered by Streamlit + Semantic Kernel

Futja e Agjentic AI ishte një shpëtimtar për mua, dhe vendosa të zgjidhja këtë kornizë si një zgjidhje. megjithatë, ka pasur sfida: cila kornizë duhet të përdoret, dhe a ka një burim të hapur në dispozicion? Sa i shtrenjtë do të ishte platforma e menaxhuar?

  • Rrëshqitje në fund të frontit,
  • Kerneli semantik për menaxhimin dhe zinxhirimin e shpejtë,
  • Azure OpenAI për përpunimin e gjuhës natyrore
  • Playwright për skrapimin e sigurt dhe dinamik të faqeve të Confluence.

Çfarë bën

Ky mjet do të mundësojë Menaxherët e Programit dhe Liderët të:

  • 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

Si funksionon - Pseudo Code

Step 1. Confluence Page LookUp.

Në vend të ngjisjes manuale të URL-ve, secili emër i ekipit harton në URL-në e tij të Konfluencës duke përdorur një fjalor. Kur një përdorues zgjedh "Team A" nga paneli i djathtë, backend automatikisht merr URL-në e lidhur dhe shkakton scraping.


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

"Team 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

Më në fund, përfundova duke përdorur Playwright për skrapimin e bazuar në shfletues pa kokë, i cili na ndihmon të ngarkojmë përmbajtjen dinamike dhe të menaxhojmë hyrjen:

Qasja e dështuar:

  • [ ] Duke përdorur bibliotekën e kërkesave Python, merrni të dhënat e konfluencës duke përdorur API. Mekanizmi i autentifikimit nuk ishte i suksesshëm.
  • [ ] Duke përdorur bibliotekën Python BeautifySoup. Ajo u përjashtua për shkak të përmbajtjes dinamike.
  • [ ]Unë përfundova me Python Playwright. shtresën SSO kishte sfida, por më në fund, ajo punoi pas shkarkimit të gjendjes HTML JSON dhe ri-përdorimin e saj.


@kernel_function(description="Scrape and return text content from a Confluence page.") async def get_confluence_page_content(self, team_name: Annotated[str, "Emri i ekipit Agile"]) -> 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.

Mjet është menduar të jetë një Enabler i Menaxhimit të Programit. Udhëzimet e Agjentit janë hartuar për të përdorur përmbajtjen e grumbulluar dhe për të prodhuar një rezultat të përshtatshëm për pyetjet PM. Udhëzimet do të ndihmojnë për të marrë output si një përmbledhje teksti ose në një format grafik. Kjo është gjithashtu një shembull i kodit të ulët.

Përveç kësaj, kam përcaktuar klientin si një agjent AI si një plugin.

Agjent_Instruksionet = “”

“”

klient = OpenAI (<Local Open Source LLM>)

chat_completion_service = OpenAIChatCompletion(ai_model_id="<>",

async_client = klientë

agjent = ChatCompletionAgent( shërbim=chat_completion_service, plugins=[ ConfluencePlugin() ], emri="ConfluenceAgent", udhëzime=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.

Unë vendosa të shtoj një klient LLM shtesë për të kontrolluar nëse futja e përdoruesit është relevante për Menaxhimin e Programit apo jo.

(model="gpt-4o", messages=[ {"role": "system", "content": "Ju jeni një gjyqtar i përmbajtjes së hyrjes së përdoruesit. Analizoni hyrjen e përdoruesit. Nëse kërkohet të fshihet faqja e brendshme COnfluence për një ekip atëherë është e lidhur me Menaxhimin e Programit. Nëse nuk është e lidhur me Menaxhimin e Programit, jepni përgjigjen por shtoni 'Falseḳ' në përgjigje. Nëse është e lidhur me Menaxhimin e Programit, shtoni 'Trueḳ' në përgjigje."}, {"role": "user", "content": user_input} ], temperatura=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.

Këtu është kodi i plotë. I fshirë detajet e mia specifike të projektit. Ne duhet të ruajmë së pari state.json për ta përdorur atë në kodin



import json import os import asyncio import pandas si pd import streamlit si st nga typing import Annotated from dotenv import load_dotenv from openai import AsyncAzureOpenAI from playwright.async_api import async_playwright from bs4 import BeautifulSoup from semantic_kernel.functions import kernel_function from typing import Annotated import re import matplotlibot.pyplot as plt from semantic_kernel.agents import ChatCompletionAgent from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion from semantic_kernel.contents import FunCallContent, FunctionResultContent, StreamingTextContent from semantic_kern

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" }

# ---- Përkufizimi i Plugin ----

#Chart bar me madhësi të përhershme def plot_bar_chart(df): status_counts = df["status"].value_counts() fig, ax = plt.subplots(figsize=(1.5, 1)) # gjerësi, lartësi në inç 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 në blu, rrotulluar axtick_params(axis='y', colors='green') # y-ticks në green st.pyplot(fig)

def extract_json_from_response(text): # Përdorni regex për të gjetur matjen e parë JSON në tekst = re.search(r"(\[\s*{.*\s*\])", tekst, re.DOTALL) nëse përputhet: kthimi match.group(1) kthehet Asnjë

klasa 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

# ---- Load API credentials ---- load_dotenv() client = AsyncAzureOpenAI( azure_endpoint="<>", api_key=os.getenv("AZURE_API_KEY"), api_version='<>' ) chat_completion_service = OpenAIChatCompletion( ai_model_id="<>", async_client=client )

AGENT_INSTRUCTIONS = """Ju jeni një program i dobishëm i menaxhimit të inteligjencës artificiale që mund të ndihmojë në nxjerrjen e informacioneve kyçe të tilla si Anëtarët e Ekipit, Karakteristikat, Epics nga një faqe konfluence.

E rëndësishme: Kur përdoruesit specifikojnë një faqe të ekipit, nxjerrni vetëm tiparet dhe Epics e atij ekipi.

Kur të fillojë biseda, prezantoni veten me këtë mesazh: "Përshëndetje! unë jam asistentja juaj e PM. unë mund t'ju ndihmoj të merrni statusin e Features dhe Epics.

Hapat që duhet të ndiqni: 1. Gjithmonë së pari thirrni `get_confluence_page_content` për të grumbulluar faqen Confluence.

  • Nëse mesazhi i përdoruesit fillon me "Team: {team_name}.", përdoreni atë {team_name} për argumentin 'team_name'. Për shembull, nëse hyrja është "Team: Raptor. Cilat janë tiparet e fundit?", 'team_name' është "Raptor". 2. Nëse përdoruesi kërkon një përmbledhje, siguroni një listë të pikave të plumbit. 3. Nëse përdoruesi kërkon një array JSON ose diagram ose plot. Pastaj menjëherë thërrisni 'summarize_confluence_data' duke përdorur përmbajtjen e skrapuar. 4. Bazuar në stilin e prodhimit të kërkuar nga përdoruesi, ktheni ose një array JSON ose pikët e plumbit. 5. Nëse përdoruesi nuk specifikon një stil të prodhimit, shtypni

Udhëzime: - Nëse output_style është 'bullet', ktheni përmbledhjen e pikave të plumbit. - Nëse output_style është 'json', ktheni vetëm matricën JSON të vlefshme duke hequr un karaktere dhe hapësira të printueshme nga fillimi dhe fundi. - MOS shkruani shpjegime. - MOS sugjeroni fragmente të kodit. - MOS mbështillni JSON brenda triple backticks ```json - Dërgoni vetëm matricën e pastër JSON ose listën e pikave të plumbit.

Çfarë ekipi jeni të interesuar të ju ndihmojë të planifikoni sot?"

Nëse ata përmendin një ekip të veçantë, fokusoni të dhënat tuaja në atë ekip në vend që të sugjeroni alternativa. """ agjent = ChatCompletionAgent( service=chat_completion_service, plugins=[ ConfluencePlugin() ], name="ConfluenceAgent", instructions=AGENT_INSTRUCTIONS )



# ---- Logjika kryesore async ---- async def stream_response(user_input, thread=None): html_blocks = [] full_response = [] function_calls = [] parsed_json_result = None completion = wait client.chat.completions.create( model="gpt-4o", messages=["role": "system", "content": "Ju jeni një gjyqtar i përmbajtjes së hyrjes së përdoruesit. Anlyze funksionin e përdoruesit. Nëse ajo kërkon të shkarkojë të brendshme COnfluence Faqe për një ekip atëherë ajo është e lidhur me Menaxhimin e Programit. Nëse ajo nuk është e lidhur me Menaxhimin e Programit, siguroni përgjigjen, por shtoni 'False TIT' në përgjigje. Nëse është

    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, trup, [class*="css"] { font-size: 12px !important; } </style> """, unsafe_allow_html=True) # ---- Main Streamlit app ---- me left_col: st.title(" Program Management Enabler AI") st.write("Më pyesni për artikuj të ndryshëm të Programit Wiley të angazhuar.!") st.write("Unë mund t'ju ndihmojë të merrni statusin e Features dhe 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("Zgjidhni programin 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


import json import os import asyncio import pandas si pd import streamlit si st nga typing import Annotated from dotenv import load_dotenv from openai import AsyncAzureOpenAI from playwright.async_api import async_playwright from bs4 import BeautifulSoup from semantic_kernel.functions import kernel_function from typing import Annotated import re import matplotlibot.pyplot as plt from semantic_kernel.agents import ChatCompletionAgent from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion from semantic_kernel.contents import FunCallContent, FunctionResultContent, StreamingTextContent from semantic_kern

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" }

# ---- Përkufizimi i Plugin ----

#Chart bar me madhësi të përhershme def plot_bar_chart(df): status_counts = df["status"].value_counts() fig, ax = plt.subplots(figsize=(1.5, 1)) # gjerësi, lartësi në inç 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 në blu, rrotulluar axtick_params(axis='y', colors='green') # y-ticks në green st.pyplot(fig)

def extract_json_from_response(text): # Përdorni regex për të gjetur matjen e parë JSON në tekst = re.search(r"(\[\s*{.*\s*\])", tekst, re.DOTALL) nëse përputhet: kthimi match.group(1) kthehet Asnjë

klasa 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

# ---- Load API credentials ---- load_dotenv() client = AsyncAzureOpenAI( azure_endpoint="<>", api_key=os.getenv("AZURE_API_KEY"), api_version='<>' ) chat_completion_service = OpenAIChatCompletion( ai_model_id="<>", async_client=client )

AGENT_INSTRUCTIONS = """Ju jeni një program i dobishëm i menaxhimit të inteligjencës artificiale që mund të ndihmojë në nxjerrjen e informacioneve kyçe të tilla si Anëtarët e Ekipit, Karakteristikat, Epics nga një faqe konfluence.

E rëndësishme: Kur përdoruesit specifikojnë një faqe të ekipit, nxjerrni vetëm tiparet dhe Epics e atij ekipi.

Kur të fillojë biseda, prezantoni veten me këtë mesazh: "Përshëndetje! unë jam asistentja juaj e PM. unë mund t'ju ndihmoj të merrni statusin e Features dhe Epics.

Hapat që duhet të ndiqni: 1. Gjithmonë së pari thirrni `get_confluence_page_content` për të grumbulluar faqen Confluence.

  • Nëse mesazhi i përdoruesit fillon me "Team: {team_name}.", përdoreni atë {team_name} për argumentin 'team_name'. Për shembull, nëse hyrja është "Team: Raptor. Cilat janë tiparet e fundit?", 'team_name' është "Raptor". 2. Nëse përdoruesi kërkon një përmbledhje, siguroni një listë të pikave të plumbit. 3. Nëse përdoruesi kërkon një array JSON ose diagram ose plot. Pastaj menjëherë thërrisni 'summarize_confluence_data' duke përdorur përmbajtjen e skrapuar. 4. Bazuar në stilin e prodhimit të kërkuar nga përdoruesi, ktheni ose një array JSON ose pikët e plumbit. 5. Nëse përdoruesi nuk specifikon një stil të prodhimit, shtypni

Udhëzime: - Nëse output_style është 'bullet', ktheni përmbledhjen e pikave të plumbit. - Nëse output_style është 'json', ktheni vetëm matricën JSON të vlefshme duke hequr un karaktere dhe hapësira të printueshme nga fillimi dhe fundi. - MOS shkruani shpjegime. - MOS sugjeroni fragmente të kodit. - MOS mbështillni JSON brenda triple backticks ```json - Dërgoni vetëm matricën e pastër JSON ose listën e pikave të plumbit.

Çfarë ekipi jeni të interesuar të ju ndihmojë të planifikoni sot?"

Nëse ata përmendin një ekip të veçantë, fokusoni të dhënat tuaja në atë ekip në vend që të sugjeroni alternativa. """ agjent = ChatCompletionAgent( service=chat_completion_service, plugins=[ ConfluencePlugin() ], name="ConfluenceAgent", instructions=AGENT_INSTRUCTIONS )



# ---- Logjika kryesore async ---- async def stream_response(user_input, thread=None): html_blocks = [] full_response = [] function_calls = [] parsed_json_result = None completion = wait client.chat.completions.create( model="gpt-4o", messages=["role": "system", "content": "Ju jeni një gjyqtar i përmbajtjes së hyrjes së përdoruesit. Anlyze funksionin e përdoruesit. Nëse ajo kërkon të shkarkojë të brendshme COnfluence Faqe për një ekip atëherë ajo është e lidhur me Menaxhimin e Programit. Nëse ajo nuk është e lidhur me Menaxhimin e Programit, siguroni përgjigjen, por shtoni 'False TIT' në përgjigje. Nëse është

    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, trup, [class*="css"] { font-size: 12px !important; } </style> """, unsafe_allow_html=True) # ---- Main Streamlit app ---- me left_col: st.title(" Program Management Enabler AI") st.write("Më pyesni për artikuj të ndryshëm të Programit Wiley të angazhuar.!") st.write("Unë mund t'ju ndihmojë të merrni statusin e Features dhe 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("Zgjidhni programin 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


Konkludimi

Streamlit-based Program Management AI chatbotndihmon ekipet të gjurmojnë karakteristikat e projektit dhe epikët nga faqet e Confluence.Semantic Kernel agentsme OpenAI GPT-4o për të grumbulluar përmbajtjen specifike të faqes Confluence duke përdorurPlaywrightShteti përdoret për autentifikim. Mjeti lejon përzgjedhjen e Programit dhe ekipit përkatës, dhe bazuar në përzgjedhjen, futja e përdoruesit do të përgjigjet. Me tiparin e Agjentic AI, ne mund të mundësojmë që LLM të jetë një asistent i vërtetë personal. Mund të jetë i fuqishëm në kufizimin e qasjes së LLM në të dhënat, por ende të shfrytëzojë tiparin e LLM të të dhënave të kufizuara. Është një shembull për të kuptuar tiparin e Agjentic AI dhe sa i fuqishëm mund të jetë.


Referencë :https://github.com/microsoft/ai-agents-for-beginners?tab=readme-ov-file

              Playwright documentation. 


Trending Topics

blockchaincryptocurrencyhackernoon-top-storyprogrammingsoftware-developmenttechnologystartuphackernoon-booksBitcoinbooks