338 ukufunda
338 ukufunda

Yintoni i-AI ivula ukulawulwa kwe-Agile Program kunye ne-Confluence & Streamlit

nge Ravikant Singh23m2025/06/14
Read on Terminal Reader

Inde kakhulu; Ukufunda

Umthengisi we-AI inikeza kunye nokufaka iinkcukacha zeeprojekthi evela kwi-Confluence, ukunciphisa ukuphathwa kwiprogram ye-Agile nge-chat-powered reports, charts, kunye neengxaki.
featured image - Yintoni i-AI ivula ukulawulwa kwe-Agile Program kunye ne-Confluence & Streamlit
Ravikant Singh HackerNoon profile picture
0-item
1-item
2-item

Problem Statement:

Ukubuyekezwa kweproblem:

Nangona i-Copilot ifumaneka kwizicelo zayo, ukufumana iinkcukacha ezisetyenzisiweyo ezisetyenziswa kwifayile, i-SharePoint kunye nezinye izilwanyana ezinokufumaneka kubonakala lula kakhulu. Ndingathanda kakhulu kule umgangatho we-Gen AI. Enye ngosuku, ndingathanda ukubuyekeza zonke iimpawu (i-team deliverables for a Quarter in Agile Framework) kunye neempawu zayo ezisebenza kwi-team. Ngokuqhathelekileyo, i-Copilot ibonakele ukufumana iinkcukacha evela kwi-Confluence page, nto leyo elungileyo kunye nokufumaneka. Iinkqubo ezininzi, iiprojekthi kunye ne-updates zokusetyenziswa kwi-Confluence pages. Ukufum


Solution: AI Agentic Assistant Powered by Streamlit + Semantic Kernel

Isisombululo: I-AI Agentic Assistant isetyenziswe yi-Streamlit + Semantic Kernel

Ukusungulwa kwe-Agentic AI yaba yaye ndingathanda, kwaye ndingathanda ukhethe le framework njengoko isisombululo. Nangona kunjalo, kukho iingxaki: le framework kufuneka isetyenziswe, kwaye kukho isisombululo olufanelekileyo? Yintoni, kunye nezifundo ezininzi, ndingathanda ukuba uqhagamshelane ne-open-source kwaye usebenzisa i-tech stack elandelayo ukuvelisa i-AI assistant elula usebenzisa:

  • Streamlit kwi-front end,
  • I-Kernel yeSemantic yeManagement kunye neChain,
  • I-Azure OpenAI ye-Natural Language Processing,
  • I-Playwright ye-secure kunye ne-dynamic web scraping yeephepha ze-Confluence.

Yintoni it

Le mveliso uya kukunceda umlawuli we-Program kunye nabasebenzi:

  • 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

Indlela yokusebenza - Pseudo Code

Step 1. Confluence Page LookUp.

Kwimeko yokufaka i-URL ngamanzi, yonke igama lomsebenzi lithathwe kwi-URL yayo ye-Confluence usebenzisa i-dictionary. Xa umdlali ukhethe i-"Team A" kwi-panel elungileyo, i-backend yenza ngokuzenzakalelayo i-URL eyongezwayo kwaye yenza ukuchithwa.


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

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

Oku kuthatha ixesha elide. Okugqibela, ndiza kusetyenziswa kwe-Playwright kwi-headless browser-based scraping, nto leyo inokunceda ukulayisha iinkcukacha ze-dynamic kunye nokulawula ukuhlaziywa:

Umgangatho wokugqibela:

  • [ ]Ukusebenzisa i-Python request library, ufumane i-Confluence Data usebenzisa i-API. I-mechanism ye-authentication ayikho. Ngaphandle kwalokho, kunokuba yindlela elungileyo yokufumana i-Confluence page data.
  • [ ]Ukusebenzisa i-Python BeautifySoup library. Iye ilawulwa ngenxa yeengxaki ze-dynamic.
  • [ ]I ended up with Python Playwright. The SSO layer had challenges, but finally, it worked after downloading the HTML state JSON and reusing it.


@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.

Isixhobo yenzelwe ukuba yi-Program Management Enabler. I-Agent Instructions yenzelwe ukusetyenziswa kweengxaki ezincinane kunye nokwenza imiphumo efanelekileyo kwiingxaki ze-PM. Iingxaki ziya kukunceda ukufumana i-output njenge-text summary okanye kwi-chart format. Oku kwakhona isibonelo ye-low code.

Ukongezelela, ndicinga i-client njenge-AI Agent njenge-plugin.

AGENT_INSTRUCTIONS = “”

“”

client = OpenAI(<Local open source LLM>)

chat_completion_service = OpenAIChatCompletion(ai_model_id="<>",

async_client = umxhasi )

i-agent = ChatCompletionAgent( service=chat_completion_service, i-plugins=[ ConfluencePlugin() ], igama="ConfluenceAgent", imiyalezo=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.

Ndingathanda ukongeza i-LLM client eyongezelela ukuba ingxelo lwabasebenzisi i-Relevant to Program Management okanye akukho.

ekugqibeleni = await client.chat.completions.create( model="gpt-4o", messages=[ {"role": "system", "content": "Uyazi lwekhwalithi yengxaki yengxaki yengxaki yengxaki yengxaki yengxaki yengxaki yengxaki yengxaki yengxaki yengxaki yengxaki yengxaki yengxaki yengxaki yengxaki yengxaki yengxaki yengxaki yengxaki yengxaki yengxaki yengxaki yengxaki yengxaki yengxaki yengxaki yengxaki yengxaki yengxaki yengxaki yengxaki yengxaki yengxaki yengxaki yengxaki yengxaki y

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.

Ndiyathanda ikhowudi epheleleyo. Ndiyathanda iinkcukacha zayo zokusetyenziswa kwiprojekthi. Thina kuqala ukugcina i-state.json ukuze isetyenziswe kwi-code



ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso

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

# ---- Plugin umzekelo ----

#Bar chart kunye ubungakanani efanelekileyo def plot_bar_chart(df): status_counts = df["status"].value_counts() fig, ax = plt.subplots(figsize=(1.5, 1)) # ububanzi, ubude kwi intshi 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 kwi blue, rotated axtick_params(axis='y', colors='green') # y-ticks kwi green st.pyplot(fig)

def extract_json_from_response(text): # Usebenzisa i-regex ukufumana i-array yokuqala ye-JSON kwi-text match = re.search(r"(\[\s*{.*\s*\])", text, re.DOTALL) ukuba uyifana: return match.group(1) return None

ubungakanani 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 )

I-AGENT_INSTRUCTIONS = ""I-You is a helpful Program Management AI Agent that can help extract key information such as Team Member, Features, Epics from a confluence page.

Important: Xa abasebenzisi zibonise i-Team page, ukuthatha kuphela i-Features kunye ne-Epic ye-Team.

Xa umxholo uqala, nceda uqhagamshelane na le nqakraza: "Hello! Ndingathanda u-PM yakho. Ndiza kukunceda ukufumana i-Status ye-Features kunye ne-Epics.

Iingxaki ZOKUSEBENZA: 1. Zonke iingxaki zokuqala 'get_confluence_page_content' ukuze uqhagamshelane le page Confluence.

  • Ukuba umyalezo wabasebenzisi uqala nge "Team: {team_name}.", usebenzisa le {team_name} kwi-argument 'team_name'. Umzekelo, ukuba umyalezo uye "Team: Raptor. Yintoni iimpawu ezidlulileyo?", i 'team_name' uye "Raptor". 2. Ukuba usebenzisa umyalezo, uthathe umyalezo we-bullet points. 3. Ukuba usebenzisa umyalezo we-JSON array okanye chart okanye plot. Emva koko uqhagamshelane 'summarize_confluence_data' ukusetyenziswa kweengxaki. 4. Ngokusekelwe umyalezo we-output efunyenwe ngu-username, uthathe umyalezo we-JSON okanye i-bullet points. 5. Ukuba umyalezo wabasetyenziswa umyalezo

Ukucaciswa: - Ukuba output_style yi 'bullet', faka iiphakheji yeephakheji. - Ukuba output_style yi 'json', faka kuphela i-JSON array efanelekileyo ngokucacisa iimpawu kunye neengxaki ze-un yokucacisa kwi-beginning kunye ne-end. - ISIKHUTHA i-explanations. - ISIKHUTHA iiphakheji ze-code. - ISIKHUTHA i-JSON ngaphakathi kweephakheji zeephakheji zeephakheji ```json - I-Output ONLY i-pure JSON array okanye iiphakheji yeephakheji.

Yintoni iqela uya kubandakanyeka ukuncedisa ngoku?"

Uyakwazi ukuhlaziywa kweempawu ze-username. Ukuba zibonise iqela elifanelekileyo, nqakraza idatha yakho kwiqela elifanelekileyo kunokuba ibonise izixazululo. """ agent = ChatCompletionAgent( service=chat_completion_service, plugins=[ 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": "system", "content": "Uyazi lwekhwalithi lwekhwalithi wekhwalithi wekhwalithi. Anlyze umsebenzi wekhwalithi. 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 answer but add 'False T' to the answer. If it is related to Program Management, add 'True T' to the

    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, umzimba, [class*="css"] { font-size: 12px !important; } </style> """, unsafe_allow_html=True) # ---- Main Streamlit app ---- nge left_col: st.title(" Program Management Enabler AI") st.write("Ukuza nami malunga izinto ezahlukeneyo Wiley Program!") st.write("Ndiya kukunceda ukufumana imeko Functions kunye 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("Ukuhlola i-Wiley Program")

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


ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso ubhaliso

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

# ---- Plugin umzekelo ----

#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 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 ax.tick_params(axis='y', colors='green') # y-ticks in green st.pyplot(fig)

def extract_json_from_response(text): # Usebenzisa i-regex ukufumana i-array yokuqala ye-JSON kwi-text match = re.search(r"(\[\s*{.*\s*\])", text, re.DOTALL) ukuba uyifana: return match.group(1) return None

isixeko Confluence: definit(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 )

I-AGENT_INSTRUCTIONS = ""I-You is a helpful Program Management AI Agent that can help extract key information such as Team Member, Features, Epics from a confluence page.

Important: Xa abasebenzisi zibonise i-Team page, ukuthatha kuphela i-Features kunye ne-Epic ye-Team.

Xa umxholo uqala, nceda uqhagamshelane na le nqakraza: "Hello! Ndingathanda u-PM yakho. Ndiza kukunceda ukufumana i-Status ye-Features kunye ne-Epics.

Iingxaki ZOKUSEBENZA: 1. Zonke iingxaki zokuqala 'get_confluence_page_content' ukuze uqhagamshelane le page Confluence.

  • Ukuba umyalezo wabasebenzisi uqala nge "Team: {team_name}.", usebenzisa le {team_name} kwi-argument 'team_name'. Umzekelo, ukuba umyalezo uye "Team: Raptor. Yintoni iimpawu ezidlulileyo?", i 'team_name' uye "Raptor". 2. Ukuba usebenzisa umyalezo, uthathe umyalezo we-bullet points. 3. Ukuba usebenzisa umyalezo we-JSON array okanye chart okanye plot. Emva koko uqhagamshelane 'summarize_confluence_data' ukusetyenziswa kweengxaki. 4. Ngokusekelwe umyalezo we-output efunyenwe ngu-username, uthathe umyalezo we-JSON okanye i-bullet points. 5. Ukuba umyalezo wabasetyenziswa umyalezo

Ukucaciswa: - Ukuba output_style yi 'bullet', faka iiphakheji yeephakheji. - Ukuba output_style yi 'json', faka kuphela i-JSON array efanelekileyo ngokucacisa iimpawu kunye neengxaki ze-un yokucacisa kwi-beginning kunye ne-end. - ISIKHUTHA i-explanations. - ISIKHUTHA iiphakheji ze-code. - ISIKHUTHA i-JSON ngaphakathi kweephakheji zeephakheji zeephakheji ```json - I-Output ONLY i-pure JSON array okanye iiphakheji yeephakheji.

Yintoni iqela uya kubandakanyeka ukuncedisa ngoku?"

Uyakwazi ukuhlaziywa kweempawu ze-username. Ukuba zibonise iqela elifanelekileyo, nqakraza idatha yakho kwiqela elifanelekileyo kunokuba ibonise izixazululo. """ agent = ChatCompletionAgent( service=chat_completion_service, plugins=[ 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": "system", "content": "Uyazi lwekhwalithi lwekhwalithi wekhwalithi wekhwalithi. Anlyze umsebenzi wekhwalithi. 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 answer but add 'False T' to the answer. If it is related to Program Management, add 'True T' to the

    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, umzimba, [class*="css"] { font-size: 12px !important; } </style> """, unsafe_allow_html=True) # ---- Main Streamlit app ---- nge left_col: st.title(" Program Management Enabler AI") st.write("Ukuza nami malunga izinto ezahlukeneyo Wiley Program!") st.write("Ndiya kukunceda ukufumana imeko Functions kunye 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("Ukuhlola i-Wiley Program")

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


Ukucinga

YintoniStreamlit-based Program Management AI chatbotinikeza iindidi ukuyifaka iimpawu zeeprojekthi kunye neepics kwiiphepha zeConfluence. Le app isebenzisaSemantic Kernel agentskunye ne-OpenAI GPT-4o ukusika i-Team-specific Confluence page content usebenzisaPlaywright. I-State isetyenziselwa ukulayishwa. I-tool ivumela ukwahlukanisa i-Program kunye neqela elifanayo, kwaye phantsi kolawulo, ingxelo lomsebenzisi iya kuxhaswa. Nge-Agentic AI feature, sinokukwazi ukwenza i-LLM yobugcisa yobugcisa yobugcisa. I-LLM inokukwazi ukunciphisa ukufikelela kwe-LLM kwiinkcukacha, kodwa nangokufumana i-LLM feature yeedatha ezincinciphekileyo. Yinto isibonelo yokufumana i-Agentic AI feature kunye ne-power it.


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

              Playwright documentation. 


Trending Topics

blockchaincryptocurrencyhackernoon-top-storyprogrammingsoftware-developmenttechnologystartuphackernoon-booksBitcoinbooks