"""Streamlit front‑end for the Sustainable Finance Hub. This module implements an interactive web interface for the AI‑driven Sustainable Finance Hub using Streamlit. It supports multiple languages (English and Vietnamese), allows financial providers to register their profiles, lets SMEs and project owners submit proposals and receive funding recommendations, provides a searchable database of providers, offers AI‑powered consultation on green finance topics, and features information about the Green Transformation and Sustainability (GXS) Network. AI translation and advice use Groq’s OpenAI‑compatible API—users must supply a Groq API key (available at https://console.groq.com/keys). To run this application, install the required packages via ``pip install -r requirements.txt`` and execute ``streamlit run sustainable_finance_hub_gui.py``. """ from __future__ import annotations import json import os import tempfile from typing import List, Dict import requests import streamlit as st # type: ignore # Ensure local module imports work import sys sys.path.append(os.path.dirname(os.path.abspath(__file__))) from sustainable_finance_hub import ( SustainableFinanceHub, FinancialProvider, ProjectProposal, ) # ----------------------------------------------------------------------------- # Groq API utilities # # We reuse the same call_groq_chat and translation helpers from the planner # application. These functions send messages to the Groq API using the # OpenAI‑compatible endpoint. See ``sustainability_planner_gui.py`` for # additional documentation. def call_groq_chat( api_key: str, messages: List[Dict[str, str]], model: str = "llama-3.3-70b-versatile", temperature: float = 0.7, max_tokens: int = 512, ) -> str: url = "https://api.groq.com/openai/v1/chat/completions" headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json", } payload = { "model": model, "messages": messages, "temperature": temperature, "max_tokens": max_tokens, } try: response = requests.post(url, headers=headers, data=json.dumps(payload), timeout=60) except Exception as exc: return f"Error contacting Groq API: {exc}" if response.status_code != 200: return f"Groq API returned status {response.status_code}: {response.text}" try: res_json = response.json() return res_json["choices"][0]["message"]["content"] except Exception: return f"Unexpected response from Groq API: {response.text}" def translate_text_to_vietnamese(text: str, api_key: str) -> str: if not api_key: return text + "\n\n[Chú ý: Không có khóa API để dịch. Nội dung vẫn bằng tiếng Anh.]" messages = [ {"role": "system", "content": "Bạn là một dịch giả chuyên nghiệp từ tiếng Anh sang tiếng Việt."}, {"role": "user", "content": f"Hãy dịch đoạn văn sau sang tiếng Việt:\n\n{text}"}, ] return call_groq_chat(api_key, messages) # ----------------------------------------------------------------------------- # UI translations # # Map UI labels and messages to Vietnamese equivalents. If a key is # missing for a given language, the English version is used as a # fallback. UI_TRANSLATIONS: Dict[str, Dict[str, str]] = { "Sustainable Finance Hub": {"vi": "Trung Tâm Tài Chính Bền Vững"}, "Welcome to the Sustainable Finance Hub! Select a module from the sidebar.": { "vi": "Chào mừng đến với Trung tâm Tài chính Bền vững! Chọn một chức năng từ thanh bên." }, "Language / Ngôn ngữ": {"vi": "Ngôn ngữ / Language"}, "Home": {"vi": "Trang chính"}, "AI Consultation": {"vi": "Tư vấn AI"}, "Add Financial Provider": {"vi": "Thêm Nhà cung cấp tài chính"}, "Search Providers": {"vi": "Tìm nhà cung cấp"}, "Add Project & Recommendations": {"vi": "Thêm dự án & Gợi ý"}, "GXS Network": {"vi": "Mạng lưới GXS"}, # Consultation "Ask your question about green taxonomies, green/climate finance, sustainable finance and investing in Southeast Asia.": { "vi": "Đặt câu hỏi của bạn về hệ thống phân loại xanh, tài chính/khí hậu xanh, tài chính bền vững và đầu tư tại Đông Nam Á." }, "Enter your Groq API key": { "vi": "Nhập khóa API Groq của bạn" }, "Submit Question": {"vi": "Gửi câu hỏi"}, "AI Answer": {"vi": "Phản hồi của AI"}, "Please enter your API key before submitting.": { "vi": "Vui lòng nhập khóa API trước khi gửi." }, # Add provider form "Provider Name": {"vi": "Tên nhà cung cấp"}, "Provider Type (bank, fund, VC, etc.)": {"vi": "Loại nhà cung cấp (ngân hàng, quỹ, VC, v.v.)"}, "Description": {"vi": "Mô tả"}, "Available Funds (numeric)": {"vi": "Quỹ sẵn có (số)"}, "Sectors (comma‑separated)": {"vi": "Ngành (phân tách bằng dấu phẩy)"}, "Location/Region": {"vi": "Địa điểm/Vùng"}, "Languages (comma‑separated codes)": {"vi": "Ngôn ngữ (mã phân tách bằng dấu phẩy)"}, "Add Provider": {"vi": "Thêm nhà cung cấp"}, "Provider added successfully!": {"vi": "Thêm nhà cung cấp thành công!"}, # Search providers "Search Providers by Sector and Location": {"vi": "Tìm nhà cung cấp theo ngành và địa điểm"}, "Sector filter": {"vi": "Lọc theo ngành"}, "Location filter": {"vi": "Lọc theo địa điểm"}, "Search": {"vi": "Tìm kiếm"}, "No providers found.": {"vi": "Không tìm thấy nhà cung cấp."}, # Add project "Project Name": {"vi": "Tên dự án"}, "Project Description": {"vi": "Mô tả dự án"}, "Project Sector": {"vi": "Ngành dự án"}, "Project Location": {"vi": "Địa điểm dự án"}, "Funding Needed (numeric)": {"vi": "Vốn cần thiết (số)"}, "Project Languages (comma‑separated codes)": {"vi": "Ngôn ngữ dự án (mã phân tách bằng dấu phẩy)"}, "Submit Project": {"vi": "Gửi dự án"}, "Project submitted! See recommended providers below.": {"vi": "Đã gửi dự án! Xem các nhà cung cấp được đề xuất bên dưới."}, # Recommendations "Recommended Providers": {"vi": "Nhà cung cấp được đề xuất"}, # GXS "About GXS Network": {"vi": "Giới thiệu về mạng lưới GXS"}, "GXS Mission": {"vi": "Sứ mệnh GXS"}, "GXS Q&A": {"vi": "Hỏi đáp về GXS"}, "Ask a question about GXS programmes and activities": { "vi": "Đặt câu hỏi về các chương trình và hoạt động của GXS" }, "Get Answer": {"vi": "Nhận câu trả lời"}, "GXS Answer": {"vi": "Phản hồi của GXS"}, # Message shown when the Groq API key is missing "API key missing message": {"vi": "Khóa API Groq chưa được cấu hình. Vui lòng thêm khóa vào biến môi trường hoặc tệp secrets của Streamlit."}, # Additional message for environment API key usage "AI suggestions will use the Groq API key from the environment.": { "vi": "Các đề xuất AI sẽ sử dụng khóa API Groq từ biến môi trường." }, } def ui_text(key: str, lang: str) -> str: return UI_TRANSLATIONS.get(key, {}).get(lang, key) # Static information about the GXS Network, derived from the PreventionWeb # mission description【485509883276198†screenshot】. GXS_MISSION_EN = ( "Green Transformation and Sustainability Network (GXS) is a pioneering organisation driving the green economy, " "circular economy, biodiversity conservation, energy transition, social impact business, climate resilience, " "climate solutions, technology, governance, investment, sustainable development and green innovation across Vietnam " "and Southeast Asia. The GXS Network focuses on empowering local businesses and communities, building collaborative " "networks for sustainable living and development. Its primary activities include promoting green economy initiatives, " "fostering circular economy practices, and implementing climate and sustainable solutions that align with the Vietnamese " "government and international commitments." ) GXS_MISSION_VI = ( "Mạng lưới Chuyển đổi Xanh và Phát triển Bền vững (GXS) là một tổ chức tiên phong thúc đẩy kinh tế xanh, kinh tế tuần " "hoàn, bảo tồn đa dạng sinh học, chuyển dịch năng lượng, doanh nghiệp tác động xã hội, khả năng thích ứng khí hậu, " "các giải pháp khí hậu, công nghệ, quản trị, đầu tư, phát triển bền vững và đổi mới xanh trên khắp Việt Nam và Đông Nam Á. " "Mạng lưới GXS tập trung vào việc trao quyền cho các doanh nghiệp và cộng đồng địa phương, xây dựng các mạng lưới hợp tác " "để sống và phát triển bền vững. Các hoạt động chính bao gồm thúc đẩy các sáng kiến kinh tế xanh, nuôi dưỡng thực hành " "kinh tế tuần hoàn và triển khai các giải pháp khí hậu và bền vững phù hợp với các cam kết của Chính phủ Việt Nam và " "quốc tế." ) def main() -> None: st.set_page_config(page_title="Sustainable Finance Hub", layout="wide") # Language selection lang = st.sidebar.selectbox( ui_text("Language / Ngôn ngữ", "en"), options=["en", "vi"], format_func=lambda x: "English" if x == "en" else "Tiếng Việt", ) # Sidebar navigation pages = [ "Home", "AI Consultation", "Add Financial Provider", "Search Providers", "Add Project & Recommendations", "GXS Network", ] page = st.sidebar.radio( ui_text("Select a module", lang) if "Select a module" in UI_TRANSLATIONS else "Select a module", pages, format_func=lambda p: ui_text(p, lang), ) # Initialise data hub hub = SustainableFinanceHub() # Default API key: try environment variable first, then Streamlit secrets. This lets # users avoid setting an OS environment variable by defining GROQ_API_KEY in # .streamlit/secrets.toml. If both are unset, the key will be empty. default_api_key = os.environ.get("GROQ_API_KEY") or st.secrets.get("GROQ_API_KEY", "") # Home page if page == "Home": st.title(ui_text("Sustainable Finance Hub", lang)) st.write(ui_text("Welcome to the Sustainable Finance Hub! Select a module from the sidebar.", lang)) # AI consultation page elif page == "AI Consultation": st.header(ui_text("AI Consultation", lang)) st.write(ui_text("Ask your question about green taxonomies, green/climate finance, sustainable finance and investing in Southeast Asia.", lang)) # Do not prompt for an API key; instead rely on the configured key in environment or secrets if default_api_key: st.write(ui_text("You are using our AI system completely free. Please donate us if feel it useful - information at our website footer", lang)) else: st.error(ui_text("API key missing message", lang)) question = st.text_area("", height=150) if st.button(ui_text("Submit Question", lang)): api_key = default_api_key if not api_key: st.error(ui_text("API key missing message", lang)) else: system_prompt = ( "You are an expert in green taxonomies, climate finance, sustainable finance and investing, focusing on Southeast Asia and Vietnam. Answer concisely." if lang == "en" else "Bạn là chuyên gia về hệ thống phân loại xanh, tài chính khí hậu, tài chính bền vững và đầu tư, tập trung vào Đông Nam Á và Việt Nam. Trả lời ngắn gọn bằng tiếng Việt." ) messages = [ {"role": "system", "content": system_prompt}, {"role": "user", "content": question}, ] with st.spinner("Thinking..."): answer = call_groq_chat(api_key, messages) st.subheader(ui_text("AI Answer", lang)) st.write(answer) # Add provider page elif page == "Add Financial Provider": st.header(ui_text("Add Financial Provider", lang)) with st.form(key="provider_form"): name = st.text_input(ui_text("Provider Name", lang)) provider_type = st.text_input(ui_text("Provider Type (bank, fund, VC, etc.)", lang)) description = st.text_area(ui_text("Description", lang)) funds_str = st.text_input(ui_text("Available Funds (numeric)", lang), value="0") sectors_str = st.text_input(ui_text("Sectors (comma‑separated)", lang)) location = st.text_input(ui_text("Location/Region", lang)) languages_str = st.text_input(ui_text("Languages (comma‑separated codes)", lang), value="en") submit_provider = st.form_submit_button(ui_text("Add Provider", lang)) if submit_provider: try: available_funds = float(funds_str.strip() or 0) except ValueError: available_funds = 0.0 sectors = [s.strip() for s in sectors_str.split(",") if s.strip()] languages = [l.strip() for l in languages_str.split(",") if l.strip()] provider = FinancialProvider( name=name.strip(), provider_type=provider_type.strip(), description=description.strip(), available_funds=available_funds, sectors=sectors, location=location.strip(), languages=languages or ["en"], ) hub.add_provider(provider) st.success(ui_text("Provider added successfully!", lang)) # Search providers page elif page == "Search Providers": st.header(ui_text("Search Providers", lang)) st.write(ui_text("Search Providers by Sector and Location", lang)) sector_filter = st.text_input(ui_text("Sector filter", lang)) location_filter = st.text_input(ui_text("Location filter", lang)) if st.button(ui_text("Search", lang)): results = hub.search_providers(sector_filter.strip(), location_filter.strip()) if not results: st.info(ui_text("No providers found.", lang)) else: for p in results: st.write(f"**{p.name}** ({p.provider_type})") st.write(p.description) st.write(f"{ui_text('Available Funds (numeric)', lang)}: {p.available_funds}") st.write(f"{ui_text('Sectors (comma‑separated)', lang)}: {', '.join(p.sectors)}") st.write(f"{ui_text('Location/Region', lang)}: {p.location}") st.write("---") # Add project and get recommendations page elif page == "Add Project & Recommendations": st.header(ui_text("Add Project & Recommendations", lang)) with st.form(key="project_form"): proj_name = st.text_input(ui_text("Project Name", lang)) proj_desc = st.text_area(ui_text("Project Description", lang)) proj_sector = st.text_input(ui_text("Project Sector", lang)) proj_location = st.text_input(ui_text("Project Location", lang)) funding_str = st.text_input(ui_text("Funding Needed (numeric)", lang), value="0") proj_languages = st.text_input(ui_text("Project Languages (comma‑separated codes)", lang), value="en") submit_project = st.form_submit_button(ui_text("Submit Project", lang)) if submit_project: try: funding_needed = float(funding_str.strip() or 0) except ValueError: funding_needed = 0.0 languages = [l.strip() for l in proj_languages.split(",") if l.strip()] project = ProjectProposal( name=proj_name.strip(), description=proj_desc.strip(), sector=proj_sector.strip(), location=proj_location.strip(), funding_needed=funding_needed, languages=languages or ["en"], ) hub.add_project(project) st.success(ui_text("Project submitted! See recommended providers below.", lang)) # Show recommendations recommendations = hub.recommend_providers(project) if recommendations: st.subheader(ui_text("Recommended Providers", lang)) for p in recommendations: st.write(f"**{p.name}** ({p.provider_type})") st.write(p.description) st.write(f"{ui_text('Available Funds (numeric)', lang)}: {p.available_funds}") st.write(f"{ui_text('Sectors (comma‑separated)', lang)}: {', '.join(p.sectors)}") st.write(f"{ui_text('Location/Region', lang)}: {p.location}") st.write("---") else: st.info(ui_text("No providers found.", lang)) # GXS network page elif page == "GXS Network": st.header(ui_text("GXS Network", lang)) st.subheader(ui_text("About GXS Network", lang)) mission_text = GXS_MISSION_EN if lang == "en" else GXS_MISSION_VI st.write(mission_text) # Q&A section st.subheader(ui_text("GXS Q&A", lang)) # Use API key from environment/secrets only; do not prompt for it if default_api_key: st.write(ui_text("You are using our AI system completely free. Please donate us if feel it useful - information at our website footer.", lang)) else: st.error(ui_text("API key missing message", lang)) gxs_question = st.text_area(ui_text("Ask a question about GXS programmes and activities", lang), height=150) if st.button(ui_text("Get Answer", lang)): if not default_api_key: st.error(ui_text("API key missing message", lang)) else: sys_prompt = ( "You are an expert on the Green Transformation and Sustainability (GXS) Network. Answer user questions about GXS programmes, activities and mission in Vietnam and Southeast Asia." if lang == "en" else "Bạn là chuyên gia về Mạng lưới Chuyển đổi Xanh và Phát triển Bền vững (GXS). Hãy trả lời các câu hỏi của người dùng về các chương trình, hoạt động và sứ mệnh của GXS tại Việt Nam và Đông Nam Á bằng tiếng Việt." ) messages = [ {"role": "system", "content": sys_prompt}, {"role": "user", "content": gxs_question}, ] with st.spinner("Thinking..."): ans = call_groq_chat(default_api_key, messages) st.subheader(ui_text("GXS Answer", lang)) st.write(ans) if __name__ == "__main__": main()