This is an automated email from the ASF dual-hosted git repository.

yasith pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airavata-portals.git


The following commit(s) were added to refs/heads/main by this push:
     new 8a40181e3 Integrating Qwen3 LLM with the frontend (#34)
8a40181e3 is described below

commit 8a40181e3edd9b0b4029633bcce2387fe9362b57
Author: Showmick Das <[email protected]>
AuthorDate: Fri Aug 1 23:34:53 2025 +0600

    Integrating Qwen3 LLM with the frontend (#34)
    
    * client side code added -- failed to connect to LLM
    
    * created working OpenAI MCP client
    
    * second screen for UI
    
    * text bubbles up on second screen when user hits enter
    
    * Update airavata-mcp-client-chatbot/widget/src/components/Results.tsx
    
    Co-authored-by: Copilot <[email protected]>
    
    * Update airavata-mcp-client-chatbot/widget/src/components/Chatbox.tsx
    
    removed unnecessary import
    
    Co-authored-by: Copilot <[email protected]>
    
    * Update airavata-mcp-client-chatbot/widget/src/components/Results.tsx
    
    edited typo
    
    Co-authored-by: Copilot <[email protected]>
    
    * made edits from comments given by reviewer
    
    * message
    
    * put mcp_client directory inside backend for ease of access + created 
app.py file which can call MCP client
    
    * MCP client has been integrated with React UI
    
    * Update airavata-mcp-client-chatbot/widget/src/App.tsx
    
    The response.json() call should check if the response is successful first. 
If the server returns a non-200 status, this could throw an error or return 
unexpected data.
    
    Co-authored-by: Copilot <[email protected]>
    
    * Update airavata-mcp-client-chatbot/widget/src/App.tsx
    
    The API_URL is retrieved from environment variables but there's no fallback 
or validation. If the environment variable is not set, this could result in an 
undefined URL causing fetch to fail.
    
    Co-authored-by: Copilot <[email protected]>
    
    * Update 
airavata-mcp-client-chatbot/backend/mcp_client/open_ai_mcp_client.py
    
    The line sandbox_options = SandboxOptions = { was incorrectly changed to 
sandbox_options = {. This removes the type annotation and may cause issues if 
SandboxOptions has specific validation or structure requirements.
    
    Co-authored-by: Copilot <[email protected]>
    
    * client side code added -- failed to connect to LLM
    
    * created working OpenAI MCP client
    
    * Update airavata-mcp-client-chatbot/widget/src/components/Results.tsx
    
    Co-authored-by: Copilot <[email protected]>
    
    * Update airavata-mcp-client-chatbot/widget/src/components/Chatbox.tsx
    
    removed unnecessary import
    
    Co-authored-by: Copilot <[email protected]>
    
    * Update airavata-mcp-client-chatbot/widget/src/components/Results.tsx
    
    edited typo
    
    Co-authored-by: Copilot <[email protected]>
    
    * put mcp_client directory inside backend for ease of access + created 
app.py file which can call MCP client
    
    * MCP client has been integrated with React UI
    
    * Update airavata-mcp-client-chatbot/widget/src/App.tsx
    
    The response.json() call should check if the response is successful first. 
If the server returns a non-200 status, this could throw an error or return 
unexpected data.
    
    Co-authored-by: Copilot <[email protected]>
    
    * Update airavata-mcp-client-chatbot/widget/src/App.tsx
    
    The API_URL is retrieved from environment variables but there's no fallback 
or validation. If the environment variable is not set, this could result in an 
undefined URL causing fetch to fail.
    
    Co-authored-by: Copilot <[email protected]>
    
    * Update 
airavata-mcp-client-chatbot/backend/mcp_client/open_ai_mcp_client.py
    
    The line sandbox_options = SandboxOptions = { was incorrectly changed to 
sandbox_options = {. This removes the type annotation and may cause issues if 
SandboxOptions has specific validation or structure requirements.
    
    Co-authored-by: Copilot <[email protected]>
    
    * finished the styling for the timestamps
    
    * Update README.md
    
    Added installation and usage details
    
    * Update README.md
    
    * Update airavata-mcp-client-chatbot/widget/src/components/Results.tsx
    
    Co-authored-by: Copilot <[email protected]>
    
    * Update airavata-mcp-client-chatbot/widget/src/components/Chatbox.tsx
    
    Co-authored-by: Copilot <[email protected]>
    
    * Update airavata-mcp-client-chatbot/widget/src/App.tsx
    
    Co-authored-by: Copilot <[email protected]>
    
    * new head: mcp client and servers integrated
    
    * publish new branch: integrate-features
    
    * Update 
airavata-mcp-client-chatbot/backend/mcp_client/open_ai_mcp_client.py
    
    remove duplicate import
    
    Co-authored-by: Copilot <[email protected]>
    
    * Update README.md
    
    * Update README.md
    
    * Update 
airavata-mcp-client-chatbot/backend/mcp_client/open_ai_mcp_client.py
    
    make the MCP server url an environment variable
    
    Co-authored-by: Copilot <[email protected]>
    
    * Update 
airavata-mcp-client-chatbot/backend/mcp_client/open_ai_mcp_client.py
    
    Co-authored-by: Copilot <[email protected]>
    
    * Update 
airavata-mcp-client-chatbot/backend/mcp_client/open_ai_mcp_client.py
    
    Co-authored-by: Copilot <[email protected]>
    
    * removed pycache from git's tracking
    
    * removed pycache from git's tracking
    
    * Integrating Qwen3 with frontend
    
    ---------
    
    Co-authored-by: amishasao <[email protected]>
    Co-authored-by: ddianaitzel <[email protected]>
    Co-authored-by: Amisha Sao <[email protected]>
    Co-authored-by: Copilot <[email protected]>
---
 .DS_Store                                          | Bin 0 -> 8196 bytes
 airavata-mcp-client-chatbot/README.md              |  91 +++++++-
 airavata-mcp-client-chatbot/backend/.gitignore     |   2 +
 airavata-mcp-client-chatbot/backend/app.py         |  50 ++++
 airavata-mcp-client-chatbot/backend/config.py      |   0
 .../backend/mcp_client/__init__.py                 |   1 +
 .../mcp_client/client_side_ollama_prompting.py     |  44 ++++
 .../backend/mcp_client/cybershuttle_mcp_client.py  |   0
 .../mcp_client/initial_ollama_prompting.py         |   0
 .../backend/mcp_client/mcp_config.json             |  14 ++
 .../backend/mcp_client/open_ai_mcp_client.py       | 234 +++++++++++++++++++
 .../{ => backend}/mcp_client/requirements.txt      | Bin
 .../backend/requirements.txt                       |  29 +++
 airavata-mcp-client-chatbot/widget/.env            |   1 +
 .../widget/package-lock.json                       |  75 ++++++
 airavata-mcp-client-chatbot/widget/package.json    |   3 +
 airavata-mcp-client-chatbot/widget/src/App.tsx     | 122 ++++++++--
 .../widget/src/components/Chatbox.css              |  89 ++++++++
 .../widget/src/components/Chatbox.tsx              | 254 +++++++++++++++++----
 .../widget/src/components/Chatbox2.css             | 109 +++++++++
 .../widget/src/components/FormattedMessage.tsx     | 118 ++++++++++
 .../widget/src/components/Results.css              |  45 ++++
 .../widget/src/components/Results.tsx              | 103 +++++++++
 .../widget/src/components/Results2.css             |  83 +++++++
 24 files changed, 1402 insertions(+), 65 deletions(-)

diff --git a/.DS_Store b/.DS_Store
new file mode 100644
index 000000000..1f9467ca0
Binary files /dev/null and b/.DS_Store differ
diff --git a/airavata-mcp-client-chatbot/README.md 
b/airavata-mcp-client-chatbot/README.md
index 2173594e3..328eb3d0c 100644
--- a/airavata-mcp-client-chatbot/README.md
+++ b/airavata-mcp-client-chatbot/README.md
@@ -1,2 +1,89 @@
-# Cybershuttle-AI-Chatbot
-This is the AI Chatbot that will be integrated into Cybershuttle's existing 
interface.
+# Cybershuttle LangChain + Qwen3 Integration
+
+Connecting the React frontend to the LangChain + Qwen3 agent in 
cybershuttle/mcp-server repository.
+
+## ๐Ÿš€ How to Run
+### Step 1: In 2 seperate Windows open the cybershuttle/mcp-server repository, 
and the apacha/airavata-portals repository
+
+**Terminal 1 - Your MCP Server Repository (cybershuttle/mcp-server):**
+```bash
+python src/cybershuttle_mcp_server.py
+```
+
+**Terminal 2 - Your API Server (cybershuttle/mcp-server):**
+```bash 
+python demos/langchain_api_server.py
+```
+
+### Step 2: Start the Frontend
+
+**Terminal 4 - This Repository (apache/airavata-portals):**
+```bash
+cd airavata-mcp-client-chatbot/widget
+npm start
+```
+
+## ๐Ÿ” What Each Terminal Does
+
+### Terminal 1: MCP Server
+- **Purpose**: Connects to Cybershuttle research catalog
+- **Port**: 8000
+- **Status**: Should show "Server is healthy"
+
+### Terminal 2: Your API Server
+- **Purpose**: Exposes your LangChain agent as HTTP API
+- **Port**: 5000 (replaces his OpenAI backend)
+- **Status**: Should show "Agent: Ready"
+
+### Terminal 3: React Frontend
+- **Purpose**: The web interface users interact with
+- **Port**: 3000
+- **Status**: Opens browser to localhost:3000
+
+## ๐Ÿงช Testing
+
+### Quick Test:
+```bash
+curl http://localhost:5000/api/health
+```
+
+Should return:
+```json
+{
+  "status": "healthy",
+  "agent_ready": true,
+  "ollama_running": true,
+  "mcp_server_running": true
+}
+```
+
+### Chat Test:
+```bash
+curl -X POST http://localhost:5000/api/chat \
+  -H "Content-Type: application/json" \
+  -d '{"message": "Are there neuroscience resources in Cybershuttle?"}'
+```
+
+## ๐Ÿ’ก Architecture
+
+```
+React Frontend (port 3000)
+       โ†“
+Your API Server (port 5000)
+       โ†“  
+Your LangChain Agent
+       โ†“
+Your MCP Server (port 8000)
+       โ†“
+Cybershuttle Platform
+```
+
+## ๐ŸŽฏ Success Indicators
+
+1. **MCP Server**: Shows "Server is healthy"
+2. **Ollama**: `curl localhost:11434/api/version` works
+3. **API Server**: Shows "Agent: Ready" 
+4. **Frontend**: Loads at localhost:3000
+5. **Integration**: User queries get responses from your Qwen3 agent
+
+The frontend will work exactly like before, but now powered by open-source 
Qwen3 instead of OpenAI!
\ No newline at end of file
diff --git a/airavata-mcp-client-chatbot/backend/.gitignore 
b/airavata-mcp-client-chatbot/backend/.gitignore
new file mode 100644
index 000000000..09aa33643
--- /dev/null
+++ b/airavata-mcp-client-chatbot/backend/.gitignore
@@ -0,0 +1,2 @@
+*.env
+mcp_client/__pycache__/*
\ No newline at end of file
diff --git a/airavata-mcp-client-chatbot/backend/app.py 
b/airavata-mcp-client-chatbot/backend/app.py
new file mode 100644
index 000000000..cf794f06e
--- /dev/null
+++ b/airavata-mcp-client-chatbot/backend/app.py
@@ -0,0 +1,50 @@
+from flask import Flask, request, jsonify
+from flask_cors import CORS
+import sys
+import asyncio
+import os
+from mcp_client.open_ai_mcp_client import run_agent_query, close_mcp
+
+app = Flask(__name__)
+CORS(app)
+
+
[email protected]('/api/chat', methods=['POST'])
+def chat():
+    try:
+        data = request.json
+        user_message = data.get('message', '')
+
+        print(f"Received message: {user_message}")  # Debug log
+
+        if not user_message:
+            return jsonify({"error": "No message provided"}), 400
+
+        # Call the Cybershuttle MCP client to get a response
+        print("Calling run_agent_query...")  # Debug log
+        bot_response = asyncio.run(run_agent_query(user_message))
+        print(f"Got response: {bot_response}")  # Debug log
+
+        return jsonify({
+            "response": bot_response,
+            "success": True
+        })
+
+    except Exception as e:
+        print(f"Error in chat endpoint: {str(e)}")  # Debug log
+        print(f"Error type: {type(e)}")  # Debug log
+        import traceback
+        traceback.print_exc()  # Print full stack trace
+        return jsonify({
+            "error": str(e),
+            "success": False
+        }), 500
+
+
[email protected]('/api/health', methods=['GET'])
+def health_check():
+    return jsonify({"status": "healthy"}), 200
+
+
+if __name__ == '__main__':
+    app.run(debug=True, port=5000)
diff --git a/airavata-mcp-client-chatbot/backend/config.py 
b/airavata-mcp-client-chatbot/backend/config.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/airavata-mcp-client-chatbot/backend/mcp_client/__init__.py 
b/airavata-mcp-client-chatbot/backend/mcp_client/__init__.py
new file mode 100644
index 000000000..779671ac9
--- /dev/null
+++ b/airavata-mcp-client-chatbot/backend/mcp_client/__init__.py
@@ -0,0 +1 @@
+# MCP Client package
diff --git 
a/airavata-mcp-client-chatbot/backend/mcp_client/client_side_ollama_prompting.py
 
b/airavata-mcp-client-chatbot/backend/mcp_client/client_side_ollama_prompting.py
new file mode 100644
index 000000000..39dca77b9
--- /dev/null
+++ 
b/airavata-mcp-client-chatbot/backend/mcp_client/client_side_ollama_prompting.py
@@ -0,0 +1,44 @@
+import asyncio
+import os
+from dotenv import load_dotenv
+from langchain_ollama import OllamaLLM
+from mcp_use import MCPAgent, MCPClient
+
+
+# class ToolCallingOllamaLLM(OllamaLLM):
+#     def bind_tools(self, tools):
+#         # Override to handle tool binding if necessary
+#         self.tools = tools
+#         return self
+
+
+async def main():
+    load_dotenv()
+
+    # no change needed to MCP client config
+    config = {"mcpServers": {
+        "playwright": {
+            "command": "npx",
+            "args": ["@playwright/mcp@latest"],
+            "env": {
+                "DISPLAY": ":1"
+            }
+        }
+    }}
+
+    client = MCPClient.from_dict(config)
+
+    # connect to remote Ollama server
+    llm = OllamaLLM(
+        model="qwen-4b",
+        base_url=os.getenv("OLLAMA_API_URL", "http://localhost:11434";),
+        temperature=0.1,
+        max_tokens=1000,
+    )
+
+    agent = MCPAgent(llm=llm, client=client, max_steps=30)
+    result = await agent.run("What are the best fruiting plants to grow 
indoors?")
+    print(f"\nResult: {result}")
+
+if __name__ == "__main__":
+    asyncio.run(main())
diff --git 
a/airavata-mcp-client-chatbot/backend/mcp_client/cybershuttle_mcp_client.py 
b/airavata-mcp-client-chatbot/backend/mcp_client/cybershuttle_mcp_client.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/airavata-mcp-client-chatbot/mcp_client/initial_ollama_prompting.py 
b/airavata-mcp-client-chatbot/backend/mcp_client/initial_ollama_prompting.py
similarity index 100%
rename from airavata-mcp-client-chatbot/mcp_client/initial_ollama_prompting.py
rename to 
airavata-mcp-client-chatbot/backend/mcp_client/initial_ollama_prompting.py
diff --git a/airavata-mcp-client-chatbot/backend/mcp_client/mcp_config.json 
b/airavata-mcp-client-chatbot/backend/mcp_client/mcp_config.json
new file mode 100644
index 000000000..6e062a890
--- /dev/null
+++ b/airavata-mcp-client-chatbot/backend/mcp_client/mcp_config.json
@@ -0,0 +1,14 @@
+{
+  "mcp": {
+    "servers": {
+      "filesystem": {
+        "command": "npx",
+        "args": [
+          "-y",
+          "@modelcontextprotocol/server-filesystem",
+          "C:/Apache 
Airavata/airavata-portals-mcp-client-fork/airavata-mcp-client-chatbot"
+        ]
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git 
a/airavata-mcp-client-chatbot/backend/mcp_client/open_ai_mcp_client.py 
b/airavata-mcp-client-chatbot/backend/mcp_client/open_ai_mcp_client.py
new file mode 100644
index 000000000..63145dfc4
--- /dev/null
+++ b/airavata-mcp-client-chatbot/backend/mcp_client/open_ai_mcp_client.py
@@ -0,0 +1,234 @@
+import asyncio
+import os
+import json
+import requests
+from typing import Dict, Any
+
+from langchain_openai import ChatOpenAI
+from langchain.agents import initialize_agent, AgentType
+from langchain.tools import tool
+from mcp_use import MCPClient
+from mcp_use.types.sandbox import SandboxOptions
+from dotenv import load_dotenv
+
+load_dotenv()
+
+llm = ChatOpenAI(
+    model="gpt-4o",
+    temperature=0.1,
+    max_tokens=1000
+)
+
+client = None
+agent = None
+
+MCP_SERVER_URL = os.getenv("MCP_SERVER_URL", None)
+if not MCP_SERVER_URL:
+    raise ValueError("MCP_SERVER_URL environment variable is not set.")
+
+
+# ---------------- MCP Server Function Integration ---------------- #
+
+
+def call_mcp_function(function_name: str, parameters: Dict[str, Any]) -> 
Dict[str, Any]:
+    try:
+        endpoint_map = {
+            "list_resources": "/resources",
+            "get_resource": "/resources/{resource_id}",
+            "search_resources": "/resources/search",
+            "create_dataset": "/resources/dataset",
+            "create_notebook": "/resources/notebook",
+            "create_repository": "/resources/repository",
+            "create_model": "/resources/model",
+            "list_projects": "/projects",
+            "create_project": "/projects",
+            "start_project_session": "/hub/start-session/{project_id}",
+            "list_sessions": "/sessions",
+            "get_all_tags": "/resources/tags"
+        }
+
+        method_map = {
+            "list_resources": "GET",
+            "get_resource": "GET",
+            "search_resources": "GET",
+            "create_dataset": "POST",
+            "create_notebook": "POST",
+            "create_repository": "POST",
+            "create_model": "POST",
+            "list_projects": "GET",
+            "create_project": "POST",
+            "start_project_session": "GET",
+            "list_sessions": "GET",
+            "get_all_tags": "GET"
+        }
+
+        endpoint = endpoint_map.get(function_name)
+        method = method_map.get(function_name)
+
+        if not endpoint or not method:
+            return {"error": f"Unknown function: {function_name}"}
+
+        url = f"{MCP_SERVER_URL.rstrip('/')}{endpoint}"
+
+        if function_name == "get_resource":
+            url = url.format(resource_id=parameters.get("resource_id"))
+            params = {}
+        elif function_name == "start_project_session":
+            url = url.format(project_id=parameters.get("project_id"))
+            params = {"session_name": parameters.get("session_name")}
+        elif function_name == "create_repository":
+            params = {"github_url": parameters.get("github_url")}
+        else:
+            if method == "GET":
+                params = parameters
+            else:
+                params = {}
+
+        if method == "GET":
+            response = requests.get(url, params=params)
+        elif method == "POST":
+            if function_name in ["create_dataset", "create_notebook", 
"create_model", "create_project"]:
+                response = requests.post(url, json=parameters.get("data"))
+            else:
+                response = requests.post(url, params=params)
+        else:
+            return {"error": f"Unsupported method: {method}"}
+
+        if 200 <= response.status_code < 300:
+            try:
+                return response.json() if response.content else {"message": 
"Success"}
+            except ValueError:
+                return {"message": "Success"}
+        else:
+            return {"error": f"API call failed with status 
{response.status_code}: {response.text}"}
+
+    except Exception as e:
+        return {"error": f"Failed to call MCP function: {str(e)}"}
+
+# ---------------- LangChain Tool Wrappers ---------------- #
+
+
+@tool
+def list_resources(resource_type: str = None, tags: str = None, name: str = 
None, limit: int = 10):
+    """List resources from Cybershuttle MCP server"""
+    params = {k: v for k, v in locals().items() if v is not None}
+    return call_mcp_function("list_resources", params)
+
+
+@tool
+def get_all_tags():
+    """Get all available tags from the catalog"""
+    return call_mcp_function("get_all_tags", {})
+
+
+@tool
+def list_projects():
+    """List all projects"""
+    return call_mcp_function("list_projects", {})
+
+
+@tool
+def list_sessions(status: str = None):
+    """List all sessions with optional status"""
+    params = {}
+    if status:
+        params["status"] = status
+    return call_mcp_function("list_sessions", params)
+
+
+@tool
+def create_dataset(name: str, description: str, tags: str = None):
+    """Create a new dataset"""
+    data = {
+        "name": name,
+        "description": description,
+        "tags": tags.split(",") if tags else []
+    }
+    return call_mcp_function("create_dataset", {"data": data})
+
+
+@tool
+def create_notebook(name: str, description: str, tags: str = None):
+    """Create a new notebook"""
+    data = {
+        "name": name,
+        "description": description,
+        "tags": tags.split(",") if tags else []
+    }
+    return call_mcp_function("create_notebook", {"data": data})
+
+
+@tool
+def create_repository(name: str, description: str, github_url: str):
+    """Create a new repository"""
+    data = {
+        "name": name,
+        "description": description,
+        "github_url": github_url
+    }
+    return call_mcp_function("create_repository", {"data": data})
+
+
+@tool
+def start_project_session(project_id: str, session_name: str):
+    """Start a new session for a project"""
+    params = {
+        "project_id": project_id,
+        "session_name": session_name
+    }
+    return call_mcp_function("start_project_session", params)
+
+# ---------------- Initialization and Agent Execution ---------------- #
+
+
+async def init_mcp():
+    global client, agent
+
+    server_config = {
+        "mcpServers": {
+            "everything": {
+                "command": "npx",
+                "args": ["-y", "@modelcontextprotocol/server-everything"],
+            }
+        }
+    }
+
+    sandbox_options: SandboxOptions = {
+        "api_key": os.getenv("E2B_API_KEY"),
+        "sandbox_template_id": "base"
+    }
+
+    client = MCPClient(
+        config=server_config,
+        sandbox=True,
+        sandbox_options=sandbox_options
+    )
+
+    tools = [
+        list_resources, get_all_tags, list_projects, list_sessions,
+        create_dataset, create_notebook, create_repository, 
start_project_session
+    ]
+
+    agent = initialize_agent(
+        tools=tools,
+        llm=llm,
+        agent=AgentType.OPENAI_FUNCTIONS,
+        verbose=True
+    )
+
+
+async def run_agent_query(message: str) -> str:
+    global agent
+    if agent is None:
+        await init_mcp()
+
+    # The agent.run() method is synchronous in the current configuration 
(AgentType.OPENAI_FUNCTIONS).
+    # If the agent type or configuration changes, verify whether this method 
needs to be awaited.
+    result = agent.run(message)
+    return result
+
+
+async def close_mcp():
+    global client
+    if client:
+        await client.close_all_sessions()
diff --git a/airavata-mcp-client-chatbot/mcp_client/requirements.txt 
b/airavata-mcp-client-chatbot/backend/mcp_client/requirements.txt
similarity index 100%
rename from airavata-mcp-client-chatbot/mcp_client/requirements.txt
rename to airavata-mcp-client-chatbot/backend/mcp_client/requirements.txt
diff --git a/airavata-mcp-client-chatbot/backend/requirements.txt 
b/airavata-mcp-client-chatbot/backend/requirements.txt
new file mode 100644
index 000000000..b5f3b4aa8
--- /dev/null
+++ b/airavata-mcp-client-chatbot/backend/requirements.txt
@@ -0,0 +1,29 @@
+# Flask Backend Dependencies
+flask==2.3.3
+flask-cors==4.0.0
+
+# HTTP client for API calls
+requests==2.31.0
+
+# LangChain + Qwen3 integration (from your working demo)
+langchain==0.1.0
+langchain-community==0.0.13
+langchain-ollama==0.0.1
+langchain-core==0.1.0
+
+# FastAPI (alternative server option)
+fastapi==0.104.1
+uvicorn[standard]==0.24.0
+
+# Core dependencies
+pydantic==2.5.0
+python-multipart==0.0.6
+python-dateutil==2.8.2
+
+# Development dependencies
+pytest==7.4.3
+pytest-asyncio==0.21.1
+httpx==0.25.2
+
+# Environment management
+python-dotenv==1.0.0
\ No newline at end of file
diff --git a/airavata-mcp-client-chatbot/widget/.env 
b/airavata-mcp-client-chatbot/widget/.env
new file mode 100644
index 000000000..1c630e6a8
--- /dev/null
+++ b/airavata-mcp-client-chatbot/widget/.env
@@ -0,0 +1 @@
+REACT_APP_API_URL=http://localhost:5000
diff --git a/airavata-mcp-client-chatbot/widget/package-lock.json 
b/airavata-mcp-client-chatbot/widget/package-lock.json
index b78614619..79e2f6e9d 100644
--- a/airavata-mcp-client-chatbot/widget/package-lock.json
+++ b/airavata-mcp-client-chatbot/widget/package-lock.json
@@ -18,7 +18,10 @@
         "@types/react-dom": "^19.1.6",
         "axios": "^1.11.0",
         "react": "^19.1.0",
+        "react-chatbotify": "^2.2.0",
         "react-dom": "^19.1.0",
+        "react-icons": "^5.5.0",
+        "react-router-dom": "^7.7.0",
         "react-scripts": "5.0.1",
         "typescript": "^4.9.5",
         "web-vitals": "^2.1.4"
@@ -13845,6 +13848,16 @@
         "node": ">=14"
       }
     },
+    "node_modules/react-chatbotify": {
+      "version": "2.2.0",
+      "resolved": 
"https://registry.npmjs.org/react-chatbotify/-/react-chatbotify-2.2.0.tgz";,
+      "integrity": 
"sha512-V/5z9cZZoAl82JqyDvTXuRN5w2tIYjV/u0ObLNvHYMIA2wY+5a42Uk8Ni7Oetrnde1M7qazhN7WDWPhLdmDPkA==",
+      "license": "MIT",
+      "peerDependencies": {
+        "react": ">=16.14.0",
+        "react-dom": ">=16.14.0"
+      }
+    },
     "node_modules/react-dev-utils": {
       "version": "12.0.1",
       "resolved": 
"https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz";,
@@ -13968,6 +13981,15 @@
       "integrity": 
"sha512-SN/U6Ytxf1QGkw/9ve5Y+NxBbZM6Ht95tuXNMKs8EJyFa/Vy/+Co3stop3KBHARfn/giv+Lj1uUnTfOJ3moFEQ==",
       "license": "MIT"
     },
+    "node_modules/react-icons": {
+      "version": "5.5.0",
+      "resolved": 
"https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz";,
+      "integrity": 
"sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==",
+      "license": "MIT",
+      "peerDependencies": {
+        "react": "*"
+      }
+    },
     "node_modules/react-is": {
       "version": "17.0.2",
       "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz";,
@@ -13983,6 +14005,53 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/react-router": {
+      "version": "7.7.0",
+      "resolved": 
"https://registry.npmjs.org/react-router/-/react-router-7.7.0.tgz";,
+      "integrity": 
"sha512-3FUYSwlvB/5wRJVTL/aavqHmfUKe0+Xm9MllkYgGo9eDwNdkvwlJGjpPxono1kCycLt6AnDTgjmXvK3/B4QGuw==",
+      "license": "MIT",
+      "dependencies": {
+        "cookie": "^1.0.1",
+        "set-cookie-parser": "^2.6.0"
+      },
+      "engines": {
+        "node": ">=20.0.0"
+      },
+      "peerDependencies": {
+        "react": ">=18",
+        "react-dom": ">=18"
+      },
+      "peerDependenciesMeta": {
+        "react-dom": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/react-router-dom": {
+      "version": "7.7.0",
+      "resolved": 
"https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.7.0.tgz";,
+      "integrity": 
"sha512-wwGS19VkNBkneVh9/YD0pK3IsjWxQUVMDD6drlG7eJpo1rXBtctBqDyBm/k+oKHRAm1x9XWT3JFC82QI9YOXXA==",
+      "license": "MIT",
+      "dependencies": {
+        "react-router": "7.7.0"
+      },
+      "engines": {
+        "node": ">=20.0.0"
+      },
+      "peerDependencies": {
+        "react": ">=18",
+        "react-dom": ">=18"
+      }
+    },
+    "node_modules/react-router/node_modules/cookie": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz";,
+      "integrity": 
"sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=18"
+      }
+    },
     "node_modules/react-scripts": {
       "version": "5.0.1",
       "resolved": 
"https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz";,
@@ -14889,6 +14958,12 @@
         "node": ">= 0.8.0"
       }
     },
+    "node_modules/set-cookie-parser": {
+      "version": "2.7.1",
+      "resolved": 
"https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz";,
+      "integrity": 
"sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
+      "license": "MIT"
+    },
     "node_modules/set-function-length": {
       "version": "1.2.2",
       "resolved": 
"https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz";,
diff --git a/airavata-mcp-client-chatbot/widget/package.json 
b/airavata-mcp-client-chatbot/widget/package.json
index f56979701..5ba16f649 100644
--- a/airavata-mcp-client-chatbot/widget/package.json
+++ b/airavata-mcp-client-chatbot/widget/package.json
@@ -13,7 +13,10 @@
     "@types/react-dom": "^19.1.6",
     "axios": "^1.11.0",
     "react": "^19.1.0",
+    "react-chatbotify": "^2.2.0",
     "react-dom": "^19.1.0",
+    "react-icons": "^5.5.0",
+    "react-router-dom": "^7.7.0",
     "react-scripts": "5.0.1",
     "typescript": "^4.9.5",
     "web-vitals": "^2.1.4"
diff --git a/airavata-mcp-client-chatbot/widget/src/App.tsx 
b/airavata-mcp-client-chatbot/widget/src/App.tsx
index 1bd294918..5c7b8d0d1 100644
--- a/airavata-mcp-client-chatbot/widget/src/App.tsx
+++ b/airavata-mcp-client-chatbot/widget/src/App.tsx
@@ -1,23 +1,113 @@
-import React from "react";
+import React, { useState } from "react";
+import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
 import Chatbox from "./components/Chatbox";
+import Results from "./components/Results";
+
+export interface Message {
+  id: string;
+  from: "user" | "bot";
+  text: string;
+  timestamp: Date;
+}
 
 const App: React.FC = () => {
+  const [messages, setMessages] = useState<Message[]>([]);
+
+  const addMessage = (text: string, from: "user" | "bot") => {
+    const newMessage: Message = {
+      id: Date.now().toString(),
+      from,
+      text,
+      timestamp: new Date(),
+    };
+    setMessages((prev) => [...prev, newMessage]);
+  };
+
+  const handleUserMessage = async (text: string) => {
+    addMessage(text, "user");
+
+    // add API call to the backend
+    try {
+      const API_URL = process.env.REACT_APP_API_URL || "";
+
+      if (!API_URL || API_URL.trim() === '') {
+        console.error("REACT_APP_API_URL is not set or is invalid. Please 
configure it in your environment.");
+        throw new Error("Missing or invalid API URL configuration.");
+      }
+
+      const response = await fetch(`${API_URL}/api/chat`, {
+        method: "POST",
+        headers: {
+          "Content-Type": "application/json",
+        },
+        body: JSON.stringify({ message: text }),
+      });
+
+      if (!response.ok) {
+        console.error(`Error: ${response.status} ${response.statusText}`);
+        addMessage("Sorry, something went wrong.", "bot");
+        return;
+      }
+      const data = await response.json();
+
+      if (data.success && data.response) {
+        addMessage(data.response, "bot");
+      } else {
+        addMessage("Sorry, something went wrong.", "bot");
+      }
+    } catch (error) {
+      console.error("Error calling backend:", error);
+      addMessage("Server error. Please try again later.", "bot");
+    }
+
+    // simulate bot response after a short delay
+    // setTimeout(() => {
+    //   addMessage(`Thanks for your message: "${text}"`, "bot");
+    // }, 1000);
+  };
+
   return (
-    <div
-      style={{
-        height: "100vh",
-        backgroundColor: "#f5f5f5",
-        display: "flex",
-        flexDirection: "column",
-        justifyContent: "center",
-        alignItems: "center",
-        padding: "20px",
-        textAlign: "center", // optional for centering heading text
-      }}
-    >
-      <h1>What can I do for your research?</h1>
-      <Chatbox />
-    </div>
+    <Router>
+      <div
+        style={{
+          height: "100vh",
+          backgroundColor: "#f5f5f5",
+          display: "flex",
+          flexDirection: "column",
+          justifyContent: "center",
+          alignItems: "center",
+          padding: "20px",
+          textAlign: "center",
+        }}
+      >
+        <Routes>
+          <Route
+            path="/"
+            element={
+              <>
+                <h1>What can I do for your research?</h1>
+                <Chatbox
+                  fixedBottom={false}
+                  onSend={handleUserMessage}
+                  messages={messages}
+                />
+              </>
+            }
+          />
+          <Route
+            path="/results"
+            element={
+              <>
+                <Results
+                  messages={messages}
+                  onSendMessage={handleUserMessage}
+                />
+              </>
+            }
+          />
+        </Routes>
+      </div>
+    </Router>
   );
 };
 
diff --git a/airavata-mcp-client-chatbot/widget/src/components/Chatbox.css 
b/airavata-mcp-client-chatbot/widget/src/components/Chatbox.css
new file mode 100644
index 000000000..a34bd9170
--- /dev/null
+++ b/airavata-mcp-client-chatbot/widget/src/components/Chatbox.css
@@ -0,0 +1,89 @@
+.inputContainer {
+    border-radius: 25px;
+    background: #fff;
+    box-shadow: 0px 3px 13.3px rgba(0, 0, 0, 0.25);
+    width: 890px;
+    padding: 20px;
+    margin-top: 20px;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+  }
+
+  .inputContainerFixed {
+    position: fixed;
+    bottom: 20px;
+    left: 50%;
+    transform: translateX(-50%);
+    width: 90%;
+    max-width: 720px;
+    background-color: white;
+    padding: 16px;
+    border-radius: 25px;
+    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+    display: flex;
+    flex-direction: column; /* stack elements vertically */
+    gap: 10px;
+    z-index: 1000;
+  }
+
+  
+  .inputContainerFixed .inputRow {
+    width: 100%;
+    max-width: 890px;
+    display: flex;
+    align-items: center;
+  }
+  .inputRow {
+    display: flex;
+    flex-direction: row;
+    justify-content: space-between;
+    align-items: center;
+    width: 100%;
+  }
+  
+  .inputField {
+    flex: 1;
+    height: 60%;
+    border: none;
+    font-size: 16px;
+    outline: none;
+  }
+  
+  .iconButton {
+    background: transparent;
+    border: none;
+    cursor: pointer;
+    padding: 0;
+    width: 29px;
+    height: 29px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    margin-left: 10px;
+  }
+  
+  .footerRow {
+    margin-top: 12px;
+    display: flex;
+    gap: 8px;
+    align-self: flex-start;
+  }
+  
+  .pill {
+    height: 21px;
+    border-radius: 7px;
+    border: 1px solid #000;
+    background: #fff;
+    color: #000;
+    font-family: 'Inter', sans-serif;
+    font-size: 10px;
+    font-weight: 400;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    gap: 6px;
+    padding: 0 10px;
+    flex-shrink: 0;
+  }
+  
\ No newline at end of file
diff --git a/airavata-mcp-client-chatbot/widget/src/components/Chatbox.tsx 
b/airavata-mcp-client-chatbot/widget/src/components/Chatbox.tsx
index cceb8f84f..85782b97a 100644
--- a/airavata-mcp-client-chatbot/widget/src/components/Chatbox.tsx
+++ b/airavata-mcp-client-chatbot/widget/src/components/Chatbox.tsx
@@ -1,59 +1,219 @@
-import React, { useState } from 'react';
+import React, { useState, useEffect, useRef } from "react";
+import "./Chatbox2.css";
 
-function Chatbox() {
-  const [input, setInput] = useState('');
+import { useNavigate } from "react-router-dom";
 
+// added this so that App.tsx and Results.tsx can import and use the same 
Message type
+export interface Message {
+  id: string;
+  from: "user" | "bot";
+  text: string;
+  timestamp: Date;
+}
+
+interface ChatboxProps {
+  fixedBottom?: boolean;
+  onSend?: (message: string) => void;
+  messages?: Message[];
+  showMessages?: boolean;
+}
+
+const Chatbox: React.FC<ChatboxProps> = ({
+  fixedBottom = false,
+  onSend,
+  messages = [],
+  showMessages = false,
+}) => {
+  const [input, setInput] = useState("");
+  const [isTyping, setIsTyping] = useState(false); // provides feedback when 
sending a message
+  const navigate = useNavigate();
+  const inputRef = useRef<HTMLInputElement>(null);
+
+  useEffect(() => {
+    if (inputRef.current) {
+      inputRef.current.focus();
+    }
+  }, []);
+
+  // added typing state and better validation
   const handleSend = () => {
-    if (input.trim() === '') return;
-    console.log('Send message:', input);
-    setInput('');
+    if (input.trim() === "") {
+      return;
+    }
+    setIsTyping(true);
+    if (onSend) {
+      onSend(input.trim());
+      setTimeout(() => {
+        navigate("/results");
+      }, 100);
+    } else {
+      navigate("/results", { state: { question: input.trim() } });
+    }
+
+    console.log("๐Ÿงน Clearing input"); // Debug log
+    setInput("");
+    setTimeout(() => {
+      setIsTyping(false);
+    }, 500);
   };
 
-  const styles = {
-    inputContainer: {
-      borderRadius: 25,
-      background: '#FFF',
-      boxShadow: '0px 3px 13.3px rgba(0, 0, 0, 0.25)',
-      width: 890,
-      height: 100,
-      flexShrink: 0,
-      display: 'flex',
-      alignItems: 'center',
-      padding: '0 20px',
-      marginTop: 20,
-    },
-    input: {
-      flex: 1,
-      height: '60%',
-      border: 'none',
-      fontSize: 16,
-      outline: 'none',
-    },
-    button: {
-      padding: '10px 20px',
-      marginLeft: 10,
-      borderRadius: 20,
-      border: 'none',
-      backgroundColor: '#007bff',
-      color: '#fff',
-      cursor: 'pointer',
-    },
+  // key handling (prevents form submission if multiple keys pressed)
+  const handleKeyPress = (e: React.KeyboardEvent) => {
+    if (e.key === "Enter" && !e.shiftKey) {
+      e.preventDefault();
+      handleSend();
+    }
   };
 
   return (
-    <div style={styles.inputContainer}>
-      <input
-        type="text"
-        placeholder="Ask away :)"
-        value={input}
-        onChange={(e) => setInput(e.target.value)}
-        style={styles.input}
-      />
-      <button onClick={handleSend} style={styles.button}>
-        Send
-      </button>
+    <div className={`chatbox-wrapper ${fixedBottom ? "fixed-bottom" : ""}`}>
+      {showMessages && messages.length > 0 && (
+        <div className="message-preview">
+          {messages.slice(-3).map((message) => (
+            <div key={message.id} className={`preview-message 
${message.from}`}>
+              <span>{message.text}</span>
+            </div>
+          ))}
+        </div>
+      )}
+
+      <div className={fixedBottom ? "inputContainerFixed" : "inputContainer"}>
+        <div className="inputRow">
+          <input
+            ref={inputRef}
+            type="text"
+            placeholder="Ask away :)"
+            value={input}
+            onChange={(e) => {
+              setInput(e.target.value);
+            }}
+            onKeyDown={(e) => {
+              if (e.key === "Enter" && !e.shiftKey) {
+                console.log(
+                  "โ†ฉ๏ธ Enter key detected, preventing default and calling 
handleSend"
+                ); // Debug log
+                e.preventDefault();
+                handleSend();
+              }
+            }}
+            className={`inputField ${isTyping ? "sending" : ""}`}
+            disabled={isTyping}
+          />
+          <button
+            onClick={() => {
+              console.log("๐Ÿ–ฑ๏ธ Send button clicked"); // Debug log
+              handleSend();
+            }}
+            className={`iconButton`}
+            disabled={isTyping || input.trim() === ""}
+          >
+            <svg
+              xmlns="http://www.w3.org/2000/svg";
+              width="29"
+              height="29"
+              viewBox="0 0 29 29"
+              fill="none"
+            >
+              <path
+                d="M3.625 24.1666V4.83331L26.5833 14.5L3.625 24.1666ZM6.04167 
20.5416L20.3604 14.5L6.04167 8.45831V12.6875L13.2917 14.5L6.04167 
16.3125V20.5416ZM6.04167 20.5416V14.5V8.45831V12.6875V16.3125V20.5416Z"
+                fill={isTyping ? "#ccc" : "#1D1B20"}
+              />
+            </svg>
+          </button>
+        </div>
+
+        <div className="footerRow">
+          <div className="pill">
+            <span>GPT 4o-Mini</span>
+            <svg
+              xmlns="http://www.w3.org/2000/svg";
+              width="16"
+              height="14"
+              viewBox="0 0 16 14"
+              fill="none"
+            >
+              <path
+                d="M4 5.25L8 8.75L12 5.25"
+                stroke="#1E1E1E"
+                strokeWidth="4"
+                strokeLinecap="round"
+                strokeLinejoin="round"
+              />
+            </svg>
+          </div>
+
+          <div className="pill">
+            <span>Datasets</span>
+            <svg
+              xmlns="http://www.w3.org/2000/svg";
+              width="9"
+              height="8"
+              viewBox="0 0 9 8"
+              fill="none"
+            >
+              <g clipPath="url(#clip0)">
+                <path
+                  d="M7.875 1.66669C7.875 2.21897 6.36396 2.66669 4.5 
2.66669C2.63604 2.66669 1.125 2.21897 1.125 1.66669M7.875 1.66669C7.875 1.1144 
6.36396 0.666687 4.5 0.666687C2.63604 0.666687 1.125 1.1144 1.125 1.66669M7.875 
1.66669V6.33335C7.875 6.88669 6.375 7.33335 4.5 7.33335C2.625 7.33335 1.125 
6.88669 1.125 6.33335V1.66669M7.875 4.00002C7.875 4.55335 6.375 5.00002 4.5 
5.00002C2.625 5.00002 1.125 4.55335 1.125 4.00002"
+                  stroke="#1E1E1E"
+                  strokeWidth="0.8"
+                  strokeLinecap="round"
+                  strokeLinejoin="round"
+                />
+              </g>
+              <defs>
+                <clipPath id="clip0">
+                  <rect width="9" height="8" rx="2" fill="white" />
+                </clipPath>
+              </defs>
+            </svg>
+          </div>
+
+          <div className="pill">
+            <span>Repositories</span>
+            <svg
+              xmlns="http://www.w3.org/2000/svg";
+              width="13"
+              height="13"
+              viewBox="0 0 24 24"
+              fill="none"
+              stroke="currentColor"
+              strokeWidth="2"
+              strokeLinecap="round"
+              strokeLinejoin="round"
+            >
+              <path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 
0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 
19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 
0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 
18.13V22" />
+            </svg>
+          </div>
+
+          <div className="pill">
+            <span>Notebooks</span>
+            <svg
+              xmlns="http://www.w3.org/2000/svg";
+              width="12"
+              height="11"
+              viewBox="0 0 12 11"
+              fill="none"
+            >
+              <g clipPath="url(#clip0_12_56)">
+                <path
+                  d="M6 3.20833C6 2.7221 5.78929 2.25579 5.41421 
1.91197C5.03914 1.56815 4.53043 1.375 4 1.375H1V8.25H4.5C4.89782 8.25 5.27936 
8.39487 5.56066 8.65273C5.84196 8.91059 6 9.26033 6 9.625M6 3.20833V9.625M6 
3.20833C6 2.7221 6.21071 2.25579 6.58579 1.91197C6.96086 1.56815 7.46957 1.375 
8 1.375H11V8.25H7.5C7.10218 8.25 6.72064 8.39487 6.43934 8.65273C6.15804 
8.91059 6 9.26033 6 9.625"
+                  stroke="#1E1E1E"
+                  strokeWidth="1.5"
+                  strokeLinecap="round"
+                  strokeLinejoin="round"
+                />
+              </g>
+              <defs>
+                <clipPath id="clip0_12_56">
+                  <rect width="12" height="11" fill="white" />
+                </clipPath>
+              </defs>
+            </svg>
+          </div>
+        </div>
+      </div>
     </div>
   );
-}
+};
 
 export default Chatbox;
diff --git a/airavata-mcp-client-chatbot/widget/src/components/Chatbox2.css 
b/airavata-mcp-client-chatbot/widget/src/components/Chatbox2.css
new file mode 100644
index 000000000..f1e8491a0
--- /dev/null
+++ b/airavata-mcp-client-chatbot/widget/src/components/Chatbox2.css
@@ -0,0 +1,109 @@
+.inputContainer {
+  border-radius: 25px;
+  background: #fff;
+  box-shadow: 0px 3px 13.3px rgba(0, 0, 0, 0.25);
+  width: 890px;
+  padding: 20px;
+  margin-top: 20px;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+}
+
+.inputContainerFixed {
+  position: fixed;
+  bottom: 20px;
+  left: 50%;
+  transform: translateX(-50%);
+  width: 90%;
+  max-width: 720px;
+  background-color: white;
+  padding: 16px;
+  border-radius: 25px;
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+  z-index: 1000;
+}
+
+.inputContainerFixed .inputRow {
+  width: 100%;
+  max-width: 890px;
+  display: flex;
+  align-items: center;
+}
+
+.inputRow {
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+  align-items: center;
+  width: 100%;
+}
+
+.inputField {
+  flex: 1;
+  height: 40px;
+  border: none;
+  font-size: 16px;
+  outline: none;
+  padding: 0 12px;
+}
+
+.inputField:focus {
+  outline: none;
+}
+
+/* Send button - made visible again */
+.iconButton {
+  background: transparent;
+  border: none;
+  cursor: pointer;
+  padding: 8px;
+  width: 45px;
+  height: 45px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-left: 10px;
+  border-radius: 50%;
+  transition: background-color 0.2s ease;
+}
+
+.iconButton:hover:not(:disabled) {
+  background-color: #f0f0f0;
+}
+
+.iconButton:disabled {
+  opacity: 0.5;
+  cursor: not-allowed;
+}
+
+.footerRow {
+  margin-top: 12px;
+  display: flex;
+  gap: 8px;
+  align-self: flex-start;
+}
+
+.pill {
+  height: 21px;
+  border-radius: 7px;
+  border: 1px solid #000;
+  background: #fff;
+  color: #000;
+  font-family: 'Inter', sans-serif;
+  font-size: 10px;
+  font-weight: 400;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 6px;
+  padding: 0 10px;
+  flex-shrink: 0;
+}
+
+.pill:hover {
+  background: #f8f9fa;
+}
\ No newline at end of file
diff --git 
a/airavata-mcp-client-chatbot/widget/src/components/FormattedMessage.tsx 
b/airavata-mcp-client-chatbot/widget/src/components/FormattedMessage.tsx
new file mode 100644
index 000000000..04b7319cb
--- /dev/null
+++ b/airavata-mcp-client-chatbot/widget/src/components/FormattedMessage.tsx
@@ -0,0 +1,118 @@
+import React from 'react';
+
+interface FormattedMessageProps {
+  text: string;
+}
+
+const FormattedMessage: React.FC<FormattedMessageProps> = ({ text }) => {
+  // Function to format the text with proper spacing and structure
+  const formatText = (text: string) => {
+    // Split into lines and process each one
+    const lines = text.split('\n');
+    const formattedLines: React.ReactElement[] = [];
+    
+    lines.forEach((line, index) => {
+      const trimmedLine = line.trim();
+      
+      // Skip completely empty lines but preserve single empty lines for 
spacing
+      if (trimmedLine === '') {
+        formattedLines.push(<br key={`br-${index}`} />);
+        return;
+      }
+      
+      // Handle bold text (**text**)
+      if (trimmedLine.includes('**')) {
+        const parts = trimmedLine.split('**');
+        const formatted = parts.map((part, partIndex) => {
+          if (partIndex % 2 === 1) {
+            return <strong key={`bold-${index}-${partIndex}`}>{part}</strong>;
+          }
+          return part;
+        });
+        formattedLines.push(
+          <div key={`line-${index}`} style={{ marginBottom: '8px' }}>
+            {formatted}
+          </div>
+        );
+        return;
+      }
+      
+      // Handle bullet points (lines starting with โ€ข or -)
+      if (trimmedLine.startsWith('โ€ข') || trimmedLine.startsWith('-')) {
+        formattedLines.push(
+          <div key={`bullet-${index}`} style={{ 
+            marginLeft: '16px', 
+            marginBottom: '4px',
+            display: 'flex',
+            alignItems: 'flex-start'
+          }}>
+            <span style={{ marginRight: '8px', color: '#666' }}>โ€ข</span>
+            <span>{trimmedLine.substring(1).trim()}</span>
+          </div>
+        );
+        return;
+      }
+      
+      // Handle numbered lists (1. 2. etc.)
+      const numberMatch = trimmedLine.match(/^(\d+\.)\s(.+)/);
+      if (numberMatch) {
+        formattedLines.push(
+          <div key={`numbered-${index}`} style={{ 
+            marginLeft: '16px', 
+            marginBottom: '4px',
+            display: 'flex',
+            alignItems: 'flex-start'
+          }}>
+            <span style={{ marginRight: '8px', color: '#666', fontWeight: 
'bold' }}>
+              {numberMatch[1]}
+            </span>
+            <span>{numberMatch[2]}</span>
+          </div>
+        );
+        return;
+      }
+      
+      // Handle headers (## or ### etc.)
+      if (trimmedLine.startsWith('#')) {
+        const headerLevel = (trimmedLine.match(/^#+/) || [''])[0].length;
+        const headerText = trimmedLine.replace(/^#+\s*/, '');
+        const fontSize = headerLevel === 1 ? '20px' : headerLevel === 2 ? 
'18px' : '16px';
+        const fontWeight = headerLevel <= 2 ? 'bold' : '600';
+        
+        formattedLines.push(
+          <div key={`header-${index}`} style={{ 
+            fontSize,
+            fontWeight,
+            marginTop: '16px',
+            marginBottom: '8px',
+            color: '#333'
+          }}>
+            {headerText}
+          </div>
+        );
+        return;
+      }
+      
+      // Regular paragraph
+      formattedLines.push(
+        <div key={`para-${index}`} style={{ marginBottom: '8px', lineHeight: 
'1.5' }}>
+          {trimmedLine}
+        </div>
+      );
+    });
+    
+    return formattedLines;
+  };
+
+  return (
+    <div style={{ 
+      whiteSpace: 'normal',
+      wordWrap: 'break-word',
+      maxWidth: '100%'
+    }}>
+      {formatText(text)}
+    </div>
+  );
+};
+
+export default FormattedMessage;
\ No newline at end of file
diff --git a/airavata-mcp-client-chatbot/widget/src/components/Results.css 
b/airavata-mcp-client-chatbot/widget/src/components/Results.css
new file mode 100644
index 000000000..70a5b990e
--- /dev/null
+++ b/airavata-mcp-client-chatbot/widget/src/components/Results.css
@@ -0,0 +1,45 @@
+.resultsContainer {
+  padding: 30px;
+  padding-bottom: 140px; /* Room for fixed input */
+  width: 100vw;
+  box-sizing: border-box;
+  overflow-y: auto;
+  max-height: calc(100vh - 200px); /* Adjust if needed */
+}
+  .messageRow {
+    display: flex;
+    margin: 16px 0;
+  }
+  
+  .messageRow.user {
+    justify-content: flex-end;  /* push user messages to the right */
+  }
+  
+  .messageRow.bot {
+    justify-content: flex-start; /* push bot messages to the left */
+  }
+  
+  .messageBubble {
+    border-radius: 27px;
+    padding: 16px 24px;
+    max-width: 60%;
+    font-size: 18px;
+    line-height: 1.4;
+    word-wrap: break-word;
+  }
+  
+  .messageBubble.user {
+    background-color: #F4C2BE;
+    color: black;
+  }
+  
+  .messageBubble.bot {
+    /* Remove bubble styles for bot messages */
+    display: inline;        /* just inline text */
+    background: none;       /* no background */
+    color: black;
+    border-radius: 0;       /* no rounding */
+    padding: 0;             /* no padding */
+    max-width: none;        /* no max width */
+    word-wrap: normal;      /* normal wrapping */
+  }
\ No newline at end of file
diff --git a/airavata-mcp-client-chatbot/widget/src/components/Results.tsx 
b/airavata-mcp-client-chatbot/widget/src/components/Results.tsx
new file mode 100644
index 000000000..4f3034ac7
--- /dev/null
+++ b/airavata-mcp-client-chatbot/widget/src/components/Results.tsx
@@ -0,0 +1,103 @@
+import React, { useState, useEffect, useRef } from "react";
+import { useLocation } from "react-router-dom";
+import "./Results2.css";
+import Chatbox from "./Chatbox";
+import FormattedMessage from "./FormattedMessage";
+
+export interface Message {
+  id: string;
+  from: "user" | "bot";
+  text: string;
+  timestamp: Date;
+}
+
+interface ResultsProps {
+  messages?: Message[];
+  onSendMessage?: (message: string) => void;
+}
+
+interface LocationState {
+  question: string;
+}
+
+const Results: React.FC<ResultsProps> = ({ messages = [], onSendMessage }) => {
+  const location = useLocation();
+  const state = location.state as LocationState | undefined;
+  const messagesEndRef = useRef<HTMLDivElement>(null);
+  const [displayedMessages, setDisplayedMessages] = useState<Message[]>([]);
+
+  useEffect(() => {
+    if (state?.question && messages.length === 0) {
+      const initialMessage: Message = {
+        id: Date.now().toString(),
+        from: "user",
+        text: state.question,
+        timestamp: new Date(),
+      };
+      setDisplayedMessages([initialMessage]);
+
+      // add bot response after delay
+      setTimeout(() => {
+        const botResponse: Message = {
+          id: (Date.now() + 1).toString(),
+          from: "bot",
+          text: "Hi there! Thanks for your question. How can I help you with 
your research?",
+          timestamp: new Date(),
+        };
+        setDisplayedMessages((prev) => [...prev, botResponse]);
+      }, 1000);
+    } else {
+      setDisplayedMessages(messages);
+    }
+  }, [state?.question, messages]);
+
+  useEffect(() => {
+    messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
+  }, [displayedMessages]);
+
+  const formatTime = (timestamp: Date) => {
+    return timestamp.toLocaleDateString([], {
+      hour: "2-digit",
+      minute: "2-digit",
+      second: "2-digit",
+    });
+  };
+
+  return (
+    <div className="resultsContainer">
+      <div className="chatMessages">
+        {displayedMessages.map((msg, idx) => (
+          <div
+            key={msg.id}
+            className={`messageRow ${msg.from}`}
+            style={{
+              animation: `slideIn 0.3s ease-out ${idx * 0.1}s both`,
+            }}
+          >
+            <div className="messageGroup">
+              <div className={`messageBubble ${msg.from}`}>
+                {msg.from === "bot" ? (
+                  <FormattedMessage text={msg.text} />
+                ) : (
+                  <span className="messageText">{msg.text}</span>
+                )}
+              </div>
+              <div className={`messageTimestamp ${msg.from}`}>
+                {formatTime(msg.timestamp)}
+              </div>
+            </div>
+          </div>
+        ))}
+        <div ref={messagesEndRef} />
+      </div>
+
+      <Chatbox
+        fixedBottom
+        onSend={onSendMessage}
+        messages={displayedMessages}
+      />
+    </div>
+  );
+};
+
+export default Results;
\ No newline at end of file
diff --git a/airavata-mcp-client-chatbot/widget/src/components/Results2.css 
b/airavata-mcp-client-chatbot/widget/src/components/Results2.css
new file mode 100644
index 000000000..54af4c253
--- /dev/null
+++ b/airavata-mcp-client-chatbot/widget/src/components/Results2.css
@@ -0,0 +1,83 @@
+.resultsContainer {
+  padding: 30px;
+  padding-bottom: 140px; /* Room for fixed input */
+  width: 100vw;
+  box-sizing: border-box;
+  overflow-y: auto;
+  max-height: calc(100vh - 200px);
+  background: #f5f5f5; /* Light background like your App.tsx */
+}
+
+.chatMessages {
+  display: flex;
+  flex-direction: column;
+  gap: 16px;
+}
+
+.messageRow {
+  display: flex;
+  margin: 16px 0;
+}
+
+.messageRow.user {
+  justify-content: flex-end;  /* push user messages to the right */
+}
+
+.messageRow.bot {
+  justify-content: flex-start; /* push bot messages to the left */
+}
+
+.messageBubble {
+  border-radius: 27px;
+  padding: 16px 24px;
+  max-width: 60%;
+  font-size: 18px;
+  line-height: 1.4;
+  word-wrap: break-word;
+}
+
+.messageBubble.user {
+  background-color: #F4C2BE; /* Your original user bubble color */
+  color: black;
+}
+
+.messageBubble.bot {
+  background-color: #ffffff; /* White background for bot messages */
+  color: black;
+  border: 1px solid #e0e0e0; /* Light border */
+  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); /* Subtle shadow */
+}
+
+/* Scrollbar styling (subtle) */
+.chatMessages::-webkit-scrollbar {
+  width: 6px;
+}
+
+.chatMessages::-webkit-scrollbar-track {
+  background: transparent;
+}
+
+.chatMessages::-webkit-scrollbar-thumb {
+  background: rgba(0, 0, 0, 0.2);
+  border-radius: 3px;
+}
+
+.chatMessages::-webkit-scrollbar-thumb:hover {
+  background: rgba(0, 0, 0, 0.3);
+}
+
+.messageTimestamp {
+  font-size: 12px;
+  color: #888;
+  margin-top: 4px;
+  margin-left: 6px;
+  margin-right: 6px;
+}
+
+.messageTimestamp.user {
+  text-align: right;
+}
+
+.messageTimestamp.bot {
+  text-align: left;
+}
\ No newline at end of file

Reply via email to