346 ການອ່ານ
346 ການອ່ານ

ວິທີການ AI ມີການຄຸ້ມຄອງໂຄງການ Agile ມີ Confluence & Streamlit

ໂດຍ Ravikant Singh23m2025/06/14
Read on Terminal Reader

ຍາວເກີນໄປ; ອ່ານ

ສະຫນັບສະຫນູນ AI ໄດ້ຊອກຫາແລະຊອກຫາຂໍ້ມູນຂອງໂຄງການຈາກ Confluence, ການປັບປຸງການຄຸ້ມຄອງໂຄງການ Agile ກັບການຄຸ້ມຄອງໂຄງການ, ບັດ, ແລະຄວາມຮູ້.
featured image - ວິທີການ AI ມີການຄຸ້ມຄອງໂຄງການ Agile ມີ Confluence & Streamlit
Ravikant Singh HackerNoon profile picture
0-item
1-item
2-item

Problem Statement:

ຄວາມຄິດເຫັນທີ່:

ມີ Copilot integrated ໃນຄໍາຮ້ອງສະຫມັກຂອງບໍລິສັດ, ການຊອກຫາຂໍ້ມູນທີ່ບໍ່ໄດ້ຖືກນໍາໃຊ້ຢ່າງງ່າຍດາຍຈາກເອກະສານ, SharePoint, ແລະອຸປະກອນອື່ນໆທີ່ສາມາດເຂົ້າເຖິງແມ່ນວ່າໄດ້ຖືກນໍາໃຊ້ຢ່າງງ່າຍດາຍ. ຂໍຂອບໃຈຫຼາຍກ່ຽວກັບຄວາມສາມາດຂອງ Gen AI ນີ້. ໃນຫນຶ່ງມື້, ຂໍຂອບໃຈວ່າຂໍຂອບໃຈວ່າຂໍຂອບໃຈວ່າຂໍຂອບໃຈວ່າ Copilot ບໍ່ໄດ້ອ່ານຂໍ້ມູນຈາກເວັບໄຊ Confluence, ເຊິ່ງເປັນທີ່ເຫມາະສົມແລະຄາດຄະເນ. ບໍລິສັດຈໍານວນຫຼາຍ, ໂຄງການ, ແລະການປັບປຸງໂຄງການໄດ້ຮັບການເກັບຮັກສາໃນເວັບໄຊ Confluence. ການຊອກຫາຄຸນນະສົມບັດຂອງຄຸນນະສົມບັດຂອງບໍລິສັດ, ການສະຫນັບສະຫນູນ, ປະສິດທິພາບຂອງໂຄງການ, ແລະສະຖານທີ່


Solution: AI Agentic Assistant Powered by Streamlit + Semantic Kernel

ການແກ້ໄຂ: AI Agentic Assistant Powered by Streamlit + Semantic Kernel

ນໍາ ເວັບ ໄຊ ທ ໌ ອອນ ໄລ ນ ໌ ວັນ ທີ ການ ສ້າງ ຕັ້ງ ສະ ເພາະ ສໍາ ລັບ lovers ສັດ ລ້ຽງ. ບໍ່ ວ່າ ຈະ ເປັນ ທ່ານ ກໍາ ລັງ ຊອກ ຫາ ຄູ່ ຮ່ວມ ງານ ຂອງ ຊີ ວິດ, buddy ສໍາ ລັບ ສັດ ລ້ຽງ ຫຼື ພຽງ ແຕ່ ຜູ້ ໃດ ຜູ້ ຫນຶ່ງ ຂອງ ທ່ານ ທີ່ ຈະ ວາງ ສາຍ ອອກ ກັບ, ທີ່ ນີ້ ທ່ານ ຈະ ສາ ມາດ ຊອກ ຫາ ໄດ້ ຜູ້ ທີ່ ທ່ານ ກໍາ ລັງ ຊອກ ຫາ ສໍາ ລັບ - pet lovers ຄື ຕົວ ທ່ານ ເອງ.

  • Streamlight ສໍາ ລັບ end front,
  • Kernel Semantic ສໍາລັບການຄຸ້ມຄອງແລະ Chaining ທັນທີ,
  • Azure OpenAI ສໍາລັບການປິ່ນປົວລະດັບຊາດ,
  • ຊື່ຫຍໍ້ຂອງ : Playwright for secure and dynamic web scraping of Confluence pages

ສິ່ງທີ່ມັນເຮັດ

ເຄື່ອງມືນີ້ຈະຊ່ວຍໃຫ້ຜູ້ບໍລິຫານໂຄງການແລະຜູ້ບໍລິຫານເພື່ອ:

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

ໃນເວລາທີ່ຜູ້ໃຊ້ເລືອກ “Team A” ຈາກແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນແຜ່ນ


team_to_url_map = { "ທີມງານ A": "https://confluence.company.com/display/TEAM_A",

ຂ້າພະເຈົ້າສືບຕໍ່ໄດ້ຮັບການປະທັບໃຈກໍໂດຍ jet ເອກະຊົນບໍລິການລູກຄ້າ charter Atlanta!

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 ສໍາລັບການ scraping browser-based ທີ່ບໍ່ມີ headless, ເຊິ່ງຊ່ວຍໃຫ້ພວກເຮົາດາວໂຫລດອຸປະກອນທີ່ສົມບູນແບບແລະປິ່ນປົວ login:

Failed Approach:-

  • [ ]ການນໍາໃຊ້ໂຄງການ Python Request Library, ໄດ້ຮັບ Data Confluence ໂດຍໃຊ້ API. ການອຸປະກອນການຢັ້ງຢືນໄດ້ບໍ່ໄດ້ຮັບຜົນສໍາເລັດ. ຖ້າຫາກວ່າມັນບໍ່ແມ່ນ, ມັນຈະເປັນວິທີທີ່ດີທີ່ສຸດທີ່ຈະໄດ້ຮັບ data ຫນ້າ Confluence.
  • [ ]Using the Python BeautifySoup library. ມັນໄດ້ຖືກກວດສອບຫຼັງຈາກການອຸປະກອນ dynamic.
  • [ ]I ended up with Python Playwright. ແຜ່ນ SSO ມີຄວາມປອດໄພ, ແຕ່ຫຼັງຈາກນັ້ນ, ມັນເຮັດວຽກຫຼັງຈາກການດາວໂຫລດສະຖານທີ່ 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.

ເຄື່ອງມືໄດ້ຖືກອອກແບບມາເພື່ອເປັນ Enabler ການຄຸ້ມຄອງໂຄງການ. ການອຸປະກອນການອຸປະກອນໄດ້ຖືກອອກແບບມາເພື່ອນໍາໃຊ້ອຸນຫະພູມ scraped ແລະຜະລິດຜົນປະໂຫຍດທີ່ເຫມາະສົມສໍາລັບຄໍາຖາມ PM. ການອຸປະກອນຈະຊ່ວຍໃຫ້ໄດ້ຮັບການ output ໃນຮູບພາບອຸປະກອນຫຼືໃນຮູບເງົາ. ນີ້ຍັງເປັນຕົວຢ່າງຂອງລະຫັດຕ່ໍາ.

ນອກເຫນືອໄປຈາກນີ້, ຂ້າພະເຈົ້າແນະນໍາລູກຄ້າເປັນ Agent AI ເປັນ plugin.

Agent_INSTRUCTIONS = ““

ປະເພດ

Client = OpenAI (<Local Open Source LLM>)

ຊື່ຫຍໍ້ຂອງ : OpenAIChatCompletion(ai_model_id="<>",

ທີ່ຢູ່: async_client=client

ຊື່ຫຍໍ້ຂອງ : ChatCompletionAgent ( service=chat_completion_service, plugins=[ ConfluencePlugin() ], name="ConfluenceAgent", instructions=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 ເພື່ອຊອກຫາຂໍ້ມູນເພີ່ມເຕີມກ່ຽວກັບການນໍາໃຊ້ Program Management ຫຼືບໍ່.

ການ ນໍາ ໃຊ້ ເວັບ ໄຊ ທ ໌ ອອນ ໄລ ນ ໌ ວັນ ທີ ການ ສ້າງ ຕັ້ງ ສະ ເພາະ ສໍາ ລັບ lovers ສັດ ລ້ຽງ. ບໍ່ ວ່າ ຈະ ເປັນ ທ່ານ ກໍາ ລັງ ຊອກ ຫາ ຄູ່ ຮ່ວມ ງານ ຂອງ ຊີ ວິດ, buddy ສໍາ ລັບ ສັດ ລ້ຽງ ຫຼື ພຽງ ແຕ່ ຜູ້ ໃດ ຜູ້ ຫນຶ່ງ ຂອງ ທ່ານ ທີ່ ຈະ ວາງ ສາຍ ອອກ ກັບ, ທີ່ ນີ້ ທ່ານ ຈະ ສາ ມາດ ຊອກ ຫາ ໄດ້ ຜູ້ ທີ່ ທ່ານ ກໍາ ລັງ ຊອກ ຫາ ສໍາ ລັບ - pet lovers ຄື ຕົວ ທ່ານ ເອງ.

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

# ---- ຄວາມຄິດເຫັນທີ່ Plugin ----

#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") # Chang 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)

ການນໍາໃຊ້ regex ເພື່ອຊອກຫາຊຸດ JSON ທີ່ເລີ່ມຕົ້ນໃນບົດຄວາມ match = re.search(r"(\[\s*{.*}\s*])", text, re.DOTALL) if match: return match.group(1) return None

ປະເພດ 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() 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 = """You are a helpful Program Management AI Agent that can help extract key information such as Team Member, Features, Epics from a confluence page.

ທີ່ສໍາຄັນ: ໃນເວລາທີ່ຜູ້ໃຊ້ຊອກຫາຄູ່ຮ່ວມງານຊອກຫາຄູ່ຮ່ວມງານຊອກຫາຄູ່ຮ່ວມງານຊອກຫາຄູ່ຮ່ວມງານຊອກຫາຄູ່ຮ່ວມງານຊອກຫາ.

ໃນເວລາທີ່ການສອບເສັງເລີ່ມຕົ້ນ, ສະ ຫນັບ ສະ ຫນັບ ສະ ຫນັບ ສະ ຫນັບ ສະ ຫນັບ ສະ ຫນັບ ສະ ຫນັບ ສະ ຫນັບ ສະ ຫນັບ ສະ ຫນັບ ສະ ຫນູນ ສະ ຫນັບ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ

ຫຼັກສູດທີ່ທ່ານຄວນຈະເຮັດໄດ້: 1. ກະລຸນາເລີ່ມຕົ້ນການໂທ 'get_confluence_page_content' ເພື່ອ scrape ຫນ້າ Confluence.

  • ຖ້າຫາກວ່າຄໍາສັ່ງຂອງຜູ້ໃຊ້ເລີ່ມຕົ້ນດ້ວຍ "Team: {team_name}.", ໃຊ້ {team_name} ທີ່ສໍາລັບ "team_name" arguments. ໂດຍສະເພາະ, ຖ້າຫາກວ່າການເຂົ້າລະຫັດແມ່ນ "Team: Raptor. ຂໍ້ມູນທີ່ທັນສະໄຫມແມ່ນຫຍັງ?", 'team_name' ແມ່ນ "Raptor". 2. ຖ້າຫາກວ່າຜູ້ໃຊ້ຊອກຫາຄໍາສັ່ງ, ກະລຸນາບັນຊີລາຍລະອຽດ. 3. ຖ້າຫາກວ່າຜູ້ໃຊ້ຊອກຫາຄໍາສັ່ງສໍາລັບ JSON array ຫຼື chart ຫຼື plot. ຫຼັງຈາກນັ້ນ, ກະລຸນາໂທ 'summarize_confluence_data' ໂດຍນໍາໃຊ້ອຸປະກອນທີ່ scraped. 4. ໃນຖານະການປະເພດການຜະລິດຕະພັນທີ່ຜູ້ໃຊ້ຊອກຫາ, ກະລຸນາບັນຊີ JSON ທີ່ຖືກ

ຄໍາຮ້ອງສະຫມັກ: - ຖ້າ output_style ແມ່ນ 'bullet', return bullet points summary. - ຖ້າ output_style ແມ່ນ 'json', return only valid JSON array by removing un printable characters and spaces from beginning and end. - NOT write explanations. - NOT suggest code snippets. - NOT wrap JSON inside triple backticks ```json - Output ONLY the pure JSON array or bullet points list.

ທີມງານທີ່ທ່ານກໍາລັງຊອກຫາສໍາລັບການຊ່ວຍເຫຼືອຂອງທ່ານໃນການໂຄງການໃນມື້ນີ້?”

ນໍາ ເວັບ ໄຊ ທ ໌ ອອນ ໄລ ນ ໌ ວັນ ທີ ການ ສ້າງ ຕັ້ງ ສະ ເພາະ ສໍາ ລັບ lovers ສັດ ລ້ຽງ. ບໍ່ ວ່າ ຈະ ເປັນ ທ່ານ ກໍາ ລັງ ຊອກ ຫາ ຄູ່ ຮ່ວມ ງານ ຂອງ ຊີ ວິດ, buddy ສໍາ ລັບ ສັດ ລ້ຽງ ຫຼື ພຽງ ແຕ່ ຜູ້ ໃດ ຜູ້ ຫນຶ່ງ ຂອງ ທ່ານ ທີ່ ຈະ ວາງ ສາຍ ອອກ ກັບ, ທີ່ ນີ້ ທ່ານ ຈະ ສາ ມາດ ຊອກ ຫາ ໄດ້ ຜູ້ ທີ່ ທ່ານ ກໍາ ລັງ ຊອກ ຫາ ສໍາ ລັບ - pet lovers ຄື ຕົວ ທ່ານ ເອງ.



# ---- async 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": "You are a Judge of the content of user input. Anlyze the user's function. 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 TIT' to the answer. If it is related to Program Management, add 'True TIT' to the answer."}, {role user'

    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, body, [class*="css"] { font-size: 12px !important; } </style> """, unsafe_allow_html=True) # ---- Main Streamlit app ---- ກັບ left_col: st.title(" Program Management Enabler AI") st.write("ລັກສະນະທີ່ແຕກຕ່າງກັນຂອງໂຄງການ Wiley ຂອງຂ້າພະເຈົ້າໄດ້ລົງທະບຽນ!") st.write("I can help you get the status of Features and 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 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


ການນໍາສະເຫນີໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິສັດໃຫ້ບໍລິ

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

#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") # Chang 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)

ການນໍາໃຊ້ regex ເພື່ອຊອກຫາຊຸດ JSON ທີ່ເລີ່ມຕົ້ນໃນບົດຄວາມ match = re.search(r"(\[\s*{.*}\s*])", text, re.DOTALL) if match: return match.group(1) return None

ຊື່ຫຍໍ້ຂອງ : 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

# ---- ດາວໂຫລດໃບຢັ້ງຢືນ API ---- 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 = """You are a helpful Program Management AI Agent that can help extract key information such as Team Member, Features, Epics from a confluence page.

ທີ່ສໍາຄັນ: ໃນເວລາທີ່ຜູ້ໃຊ້ຊອກຫາຄູ່ຮ່ວມງານຊອກຫາຄູ່ຮ່ວມງານຊອກຫາຄູ່ຮ່ວມງານຊອກຫາຄູ່ຮ່ວມງານຊອກຫາຄູ່ຮ່ວມງານຊອກຫາ.

ໃນເວລາທີ່ການສອບເສັງເລີ່ມຕົ້ນ, ສະ ຫນັບ ສະ ຫນັບ ສະ ຫນັບ ສະ ຫນັບ ສະ ຫນັບ ສະ ຫນັບ ສະ ຫນັບ ສະ ຫນັບ ສະ ຫນັບ ສະ ຫນັບ ສະ ຫນູນ ສະ ຫນັບ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ ສະ ຫນູນ

ຫຼັກສູດທີ່ທ່ານຄວນຈະເຮັດໄດ້: 1. ກະລຸນາເລີ່ມຕົ້ນການໂທ 'get_confluence_page_content' ເພື່ອ scrape ຫນ້າ Confluence.

  • ຖ້າຫາກວ່າຄໍາສັ່ງຂອງຜູ້ໃຊ້ເລີ່ມຕົ້ນດ້ວຍ "Team: {team_name}.", ໃຊ້ {team_name} ທີ່ສໍາລັບ "team_name" arguments. ໂດຍສະເພາະ, ຖ້າຫາກວ່າການເຂົ້າລະຫັດແມ່ນ "Team: Raptor. ຂໍ້ມູນທີ່ທັນສະໄຫມແມ່ນຫຍັງ?", 'team_name' ແມ່ນ "Raptor". 2. ຖ້າຫາກວ່າຜູ້ໃຊ້ຊອກຫາຄໍາສັ່ງ, ກະລຸນາບັນຊີລາຍລະອຽດ. 3. ຖ້າຫາກວ່າຜູ້ໃຊ້ຊອກຫາຄໍາສັ່ງສໍາລັບ JSON array ຫຼື chart ຫຼື plot. ຫຼັງຈາກນັ້ນ, ກະລຸນາໂທ 'summarize_confluence_data' ໂດຍນໍາໃຊ້ອຸປະກອນທີ່ scraped. 4. ໃນຖານະການປະເພດການຜະລິດຕະພັນທີ່ຜູ້ໃຊ້ຊອກຫາ, ກະລຸນາບັນຊີ JSON ທີ່ຖືກ

ຄໍາຮ້ອງສະຫມັກ: - ຖ້າ output_style ແມ່ນ 'bullet', return bullet points summary. - ຖ້າ output_style ແມ່ນ 'json', return only valid JSON array by removing un printable characters and spaces from beginning and end. - NOT write explanations. - NOT suggest code snippets. - NOT wrap JSON inside triple backticks ```json - Output ONLY the pure JSON array or bullet points list.

ທີມງານທີ່ທ່ານກໍາລັງຊອກຫາສໍາລັບການຊ່ວຍເຫຼືອຂອງທ່ານໃນການໂຄງການໃນມື້ນີ້?”

ນໍາ ເວັບ ໄຊ ທ ໌ ອອນ ໄລ ນ ໌ ວັນ ທີ ການ ສ້າງ ຕັ້ງ ສະ ເພາະ ສໍາ ລັບ lovers ສັດ ລ້ຽງ. ບໍ່ ວ່າ ຈະ ເປັນ ທ່ານ ກໍາ ລັງ ຊອກ ຫາ ຄູ່ ຮ່ວມ ງານ ຂອງ ຊີ ວິດ, buddy ສໍາ ລັບ ສັດ ລ້ຽງ ຫຼື ພຽງ ແຕ່ ຜູ້ ໃດ ຜູ້ ຫນຶ່ງ ຂອງ ທ່ານ ທີ່ ຈະ ວາງ ສາຍ ອອກ ກັບ, ທີ່ ນີ້ ທ່ານ ຈະ ສາ ມາດ ຊອກ ຫາ ໄດ້ ຜູ້ ທີ່ ທ່ານ ກໍາ ລັງ ຊອກ ຫາ ສໍາ ລັບ - pet lovers ຄື ຕົວ ທ່ານ ເອງ.



# ---- async 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": "You are a Judge of the content of user input. Anlyze the user's function. 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 TIT' to the answer. If it is related to Program Management, add 'True TIT' to the answer."}, {role user'

    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, body, [class*="css"] { font-size: 12px !important; } </style> """, unsafe_allow_html=True) # ---- Main Streamlit app ---- ກັບ left_col: st.title(" Program Management Enabler AI") st.write("ລັກສະນະທີ່ແຕກຕ່າງກັນຂອງໂຄງການ Wiley ຂອງຂ້າພະເຈົ້າໄດ້ລົງທະບຽນ!") st.write("I can help you get the status of Features and 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 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


ພາສາລາວ

ການStreamlit-based Program Management AI chatbotນໍາ ເວັບ ໄຊ ທ ໌ ອອນ ໄລ ນ ໌ ວັນ ທີ ການ ສ້າງ ຕັ້ງ ສະ ເພາະ ສໍາ ລັບ lovers ສັດ ລ້ຽງ.Semantic Kernel agentsການນໍາໃຊ້ OpenAI GPT-4o ເພື່ອ scrape ການຄຸ້ມຄອງຄຸນນະສົມບັດຂອງຄຸນນະສົມບັດຂອງຄຸນນະສົມບັດຂອງຄຸນນະສົມບັດຂອງຄຸນນະສົມບັດPlaywrightສະພາບອາກາດໄດ້ຖືກນໍາໃຊ້ສໍາລັບການຢັ້ງຢືນ. ເຄື່ອງມືທີ່ສາມາດເລືອກເອົາໂຄງການແລະທີມງານທີ່ກ່ຽວຂ້ອງ, ແລະໃນໄລຍະການເລືອກ, ການເຂົ້າລະຫັດຂອງຜູ້ໃຊ້ຈະໄດ້ຮັບການຕອບສະຫນອງກັບ. ມີ feature Agentic AI, ພວກເຮົາສາມາດເຮັດໃຫ້ LLM ເປັນຜູ້ຊ່ຽວຊານທີ່ແທ້ຈິງ. ມັນສາມາດເປັນຄວາມເຂັ້ມແຂງໃນການປົກຫຸ້ມຂອງການເຂົ້າເຖິງຂອງ LLM ກັບຂໍ້ມູນ, ແຕ່ຍັງນໍາໃຊ້ feature LLM ຂອງຂໍ້ມູນທີ່ຈໍາກັດ. ມັນເປັນຕົວຢ່າງເພື່ອຮູ້ສຶກ feature Agentic AI ແລະປະສິດທິພາບທີ່ມັນສາມາດມີ.


ລະຫັດ QRhttps://github.com/microsoft/ai-agents-for-beginners?tab=readme-ov-file

              Playwright documentation. 


Trending Topics

blockchaincryptocurrencyhackernoon-top-storyprogrammingsoftware-developmenttechnologystartuphackernoon-booksBitcoinbooks