JerameeUC commited on
Commit
9e1932d
·
1 Parent(s): cb71edd

4th commit Trimmed More

Browse files
app.zip ADDED
Binary file (14.1 kB). View file
 
app/app/app.py ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # app/app.py — aiohttp + Bot Framework bootstrap
3
+
4
+ import os, sys
5
+ from aiohttp import web
6
+ from pathlib import Path
7
+ from botbuilder.core import BotFrameworkAdapter, BotFrameworkAdapterSettings, TurnContext
8
+
9
+ from app.routes import init_routes
10
+ from bot import SimpleBot
11
+
12
+ # Credentials
13
+ APP_ID = os.environ.get("MicrosoftAppId") or None
14
+ APP_PASSWORD = os.environ.get("MicrosoftAppPassword") or None
15
+
16
+ adapter_settings = BotFrameworkAdapterSettings(APP_ID, APP_PASSWORD)
17
+ adapter = BotFrameworkAdapter(adapter_settings)
18
+
19
+ async def on_error(context: TurnContext, error: Exception):
20
+ print(f"[on_turn_error] {error}", file=sys.stderr, flush=True)
21
+ try:
22
+ await context.send_activity("Oops. Something went wrong!")
23
+ except Exception as send_err:
24
+ print(f"[on_turn_error][send_activity_failed] {send_err}", file=sys.stderr, flush=True)
25
+
26
+ adapter.on_turn_error = on_error
27
+
28
+ # Bot instance
29
+ bot = SimpleBot()
30
+
31
+ def create_app() -> web.Application:
32
+ app = web.Application()
33
+ init_routes(app, adapter, bot)
34
+
35
+ # Optional CORS
36
+ try:
37
+ import aiohttp_cors
38
+ cors = aiohttp_cors.setup(app, defaults={
39
+ "*": aiohttp_cors.ResourceOptions(
40
+ allow_credentials=True,
41
+ expose_headers="*",
42
+ allow_headers="*",
43
+ allow_methods=["GET","POST","OPTIONS"],
44
+ )
45
+ })
46
+ for route in list(app.router.routes()):
47
+ cors.add(route)
48
+ except Exception:
49
+ pass
50
+
51
+ # Static folder if present
52
+ static_dir = Path(__file__).parent / "static"
53
+ if static_dir.exists():
54
+ app.router.add_static("/static/", path=static_dir, show_index=True)
55
+ else:
56
+ print(f"[warn] static directory not found: {static_dir}", flush=True)
57
+
58
+ return app
59
+
60
+ app = create_app()
61
+
62
+ if __name__ == "__main__":
63
+ host = os.environ.get("HOST", "127.0.0.1") # use 0.0.0.0 in containers
64
+ port = int(os.environ.get("PORT", 3978))
65
+ web.run_app(app, host=host, port=port)
app/app/routes.py ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app/routes.py — HTTP handlers
2
+ import json
3
+ from aiohttp import web
4
+ from botbuilder.core import TurnContext
5
+ from botbuilder.schema import Activity
6
+
7
+ # Prefer project logic if available
8
+ try:
9
+ from logic import handle_text as _handle_text
10
+ except Exception:
11
+ from skills import normalize, reverse_text, is_empty
12
+ def _handle_text(user_text: str) -> str:
13
+ text = (user_text or "").strip()
14
+ if not text:
15
+ return "Please provide text."
16
+ cmd = normalize(text)
17
+ if cmd in {"help", "capabilities"}:
18
+ return "Try: reverse <text> | or just say anything"
19
+ if cmd.startswith("reverse "):
20
+ original = text.split(" ", 1)[1] if " " in text else ""
21
+ return reverse_text(original)
22
+ return f"You said: {text}"
23
+
24
+ def init_routes(app: web.Application, adapter, bot) -> None:
25
+ async def messages(req: web.Request) -> web.Response:
26
+ ctype = (req.headers.get("Content-Type") or "").lower()
27
+ if "application/json" not in ctype:
28
+ return web.Response(status=415, text="Unsupported Media Type: expected application/json")
29
+ try:
30
+ body = await req.json()
31
+ except json.JSONDecodeError:
32
+ return web.Response(status=400, text="Invalid JSON body")
33
+
34
+ activity = Activity().deserialize(body)
35
+ auth_header = req.headers.get("Authorization")
36
+
37
+ invoke_response = await adapter.process_activity(activity, auth_header, bot.on_turn)
38
+ if invoke_response:
39
+ return web.json_response(data=invoke_response.body, status=invoke_response.status)
40
+ return web.Response(status=202, text="Accepted")
41
+
42
+ async def messages_get(_req: web.Request) -> web.Response:
43
+ return web.Response(
44
+ text="This endpoint only accepts POST (Bot Framework activities).",
45
+ content_type="text/plain",
46
+ status=405
47
+ )
48
+
49
+ async def home(_req: web.Request) -> web.Response:
50
+ return web.Response(
51
+ text="Bot is running. POST Bot Framework activities to /api/messages.",
52
+ content_type="text/plain"
53
+ )
54
+
55
+ async def healthz(_req: web.Request) -> web.Response:
56
+ return web.json_response({"status": "ok"})
57
+
58
+ async def plain_chat(req: web.Request) -> web.Response:
59
+ try:
60
+ payload = await req.json()
61
+ except Exception:
62
+ return web.json_response({"error": "Invalid JSON"}, status=400)
63
+ user_text = payload.get("text", "")
64
+ reply = _handle_text(user_text)
65
+ return web.json_response({"reply": reply})
66
+
67
+ # Wire routes
68
+ app.router.add_get("/", home)
69
+ app.router.add_get("/healthz", healthz)
70
+ app.router.add_get("/api/messages", messages_get)
71
+ app.router.add_post("/api/messages", messages)
72
+ app.router.add_post("/plain-chat", plain_chat)
app/mbf_bot/app.py DELETED
@@ -1,138 +0,0 @@
1
- #!/usr/bin/env python3
2
- # app.py — aiohttp + Bot Framework Echo bot
3
-
4
- import os
5
- import sys
6
- import json
7
- from logic import handle_text
8
- from aiohttp import web
9
- from botbuilder.core import BotFrameworkAdapter, BotFrameworkAdapterSettings, TurnContext
10
- from botbuilder.schema import Activity
11
- import aiohttp_cors
12
- from pathlib import Path
13
-
14
-
15
- # -------------------------------------------------------------------
16
- # Your bot implementation
17
- # -------------------------------------------------------------------
18
- # Make sure this exists at packages/bots/echo_bot.py
19
- # from bots.echo_bot import EchoBot
20
- # Minimal inline fallback if you want to test quickly:
21
- class EchoBot:
22
- async def on_turn(self, turn_context: TurnContext):
23
- if turn_context.activity.type == "message":
24
- text = (turn_context.activity.text or "").strip()
25
- if not text:
26
- await turn_context.send_activity("Input was empty. Type 'help' for usage.")
27
- return
28
-
29
- lower = text.lower()
30
- if lower == "help":
31
- await turn_context.send_activity("Try: echo <msg> | reverse: <msg> | capabilities")
32
- elif lower == "capabilities":
33
- await turn_context.send_activity("- echo\n- reverse\n- help\n- capabilities")
34
- elif lower.startswith("reverse:"):
35
- payload = text.split(":", 1)[1].strip()
36
- await turn_context.send_activity(payload[::-1])
37
- elif lower.startswith("echo "):
38
- await turn_context.send_activity(text[5:])
39
- else:
40
- await turn_context.send_activity("Unsupported command. Type 'help' for examples.")
41
- else:
42
- await turn_context.send_activity(f"[{turn_context.activity.type}] event received.")
43
-
44
- # -------------------------------------------------------------------
45
- # Adapter / bot setup
46
- # -------------------------------------------------------------------
47
- APP_ID = os.environ.get("MicrosoftAppId") or None
48
- APP_PASSWORD = os.environ.get("MicrosoftAppPassword") or None
49
-
50
- adapter_settings = BotFrameworkAdapterSettings(APP_ID, APP_PASSWORD)
51
- adapter = BotFrameworkAdapter(adapter_settings)
52
-
53
- async def on_error(context: TurnContext, error: Exception):
54
- print(f"[on_turn_error] {error}", file=sys.stderr, flush=True)
55
- try:
56
- await context.send_activity("Oops. Something went wrong!")
57
- except Exception as send_err:
58
- print(f"[on_turn_error][send_activity_failed] {send_err}", file=sys.stderr, flush=True)
59
-
60
- adapter.on_turn_error = on_error
61
- bot = EchoBot()
62
-
63
- # -------------------------------------------------------------------
64
- # HTTP handlers
65
- # -------------------------------------------------------------------
66
- async def messages(req: web.Request) -> web.Response:
67
- # Content-Type can include charset; do a contains check
68
- ctype = (req.headers.get("Content-Type") or "").lower()
69
- if "application/json" not in ctype:
70
- return web.Response(status=415, text="Unsupported Media Type: expected application/json")
71
-
72
- try:
73
- body = await req.json()
74
- except json.JSONDecodeError:
75
- return web.Response(status=400, text="Invalid JSON body")
76
-
77
- activity = Activity().deserialize(body)
78
- auth_header = req.headers.get("Authorization")
79
-
80
- invoke_response = await adapter.process_activity(activity, auth_header, bot.on_turn)
81
- if invoke_response:
82
- # For invoke activities, adapter returns explicit status/body
83
- return web.json_response(data=invoke_response.body, status=invoke_response.status)
84
- # Acknowledge standard message activities
85
- return web.Response(status=202, text="Accepted")
86
-
87
- async def home(_req: web.Request) -> web.Response:
88
- return web.Response(
89
- text="Bot is running. POST Bot Framework activities to /api/messages.",
90
- content_type="text/plain"
91
- )
92
-
93
- async def messages_get(_req: web.Request) -> web.Response:
94
- return web.Response(
95
- text="This endpoint only accepts POST (Bot Framework activities).",
96
- content_type="text/plain",
97
- status=405
98
- )
99
-
100
- async def healthz(_req: web.Request) -> web.Response:
101
- return web.json_response({"status": "ok"})
102
-
103
- async def plain_chat(req: web.Request) -> web.Response:
104
- try:
105
- payload = await req.json()
106
- except Exception:
107
- return web.json_response({"error": "Invalid JSON"}, status=400)
108
- user_text = payload.get("text", "")
109
- reply = handle_text(user_text)
110
- return web.json_response({"reply": reply})
111
-
112
- # -------------------------------------------------------------------
113
- # App factory and entrypoint
114
- # -------------------------------------------------------------------
115
- from pathlib import Path
116
-
117
- def create_app() -> web.Application:
118
- app = web.Application()
119
- app.router.add_get("/", home)
120
- app.router.add_get("/healthz", healthz)
121
- app.router.add_get("/api/messages", messages_get)
122
- app.router.add_post("/api/messages", messages)
123
- app.router.add_post("/plain-chat", plain_chat)
124
-
125
- static_dir = Path(__file__).parent / "static"
126
- if static_dir.exists():
127
- app.router.add_static("/static/", path=static_dir, show_index=True)
128
- else:
129
- print(f"[warn] static directory not found: {static_dir}", flush=True)
130
-
131
- return app
132
-
133
- app = create_app()
134
-
135
- if __name__ == "__main__":
136
- host = os.environ.get("HOST", "127.0.0.1") # use 0.0.0.0 in containers
137
- port = int(os.environ.get("PORT", 3978))
138
- web.run_app(app, host=host, port=port)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
full/.env.example DELETED
@@ -1,9 +0,0 @@
1
- # Optional provider pinning & keys
2
- # AI_PROVIDER=hf|azure|openai|cohere|deepai|offline
3
- # HF_API_KEY=
4
- # MICROSOFT_AI_SERVICE_ENDPOINT=
5
- # MICROSOFT_AI_API_KEY=
6
- # OPENAI_API_KEY=
7
- # COHERE_API_KEY=
8
- # DEEPAI_API_KEY=
9
- # HTTP_TIMEOUT=20
 
 
 
 
 
 
 
 
 
 
full/README.md DELETED
@@ -1,13 +0,0 @@
1
- # Storefront Chatbot (Unified)
2
-
3
- This merges your skeleton with AgenticCore + a simple FastAPI backend and frontends.
4
-
5
- ## Run
6
- 1) python -m venv .venv && source .venv/bin/activate
7
- 2) pip install -r requirements.txt
8
- 3) uvicorn backend.app.main:app --reload --port 8000
9
- 4) Open http://127.0.0.1:8000/ui or open frontend/chat_minimal.html
10
-
11
- ## Test
12
- - pytest -q
13
- - python tools/smoke_test.py
 
 
 
 
 
 
 
 
 
 
 
 
 
 
full/agenticcore/__init__.py DELETED
File without changes
full/agenticcore/chatbot/__init__.py DELETED
File without changes
full/agenticcore/chatbot/services.py DELETED
@@ -1,102 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import json
4
- import os
5
- from dataclasses import dataclass
6
- from typing import Dict
7
-
8
- # Delegate sentiment to the unified provider layer
9
- # If you put providers_unified.py under agenticcore/chatbot/, change the import to:
10
- # from agenticcore.chatbot.providers_unified import analyze_sentiment
11
- from agenticcore.providers_unified import analyze_sentiment
12
- from ..providers_unified import analyze_sentiment
13
-
14
-
15
- def _trim(s: str, max_len: int = 2000) -> str:
16
- s = (s or "").strip()
17
- return s if len(s) <= max_len else s[: max_len - 1] + "…"
18
-
19
-
20
- @dataclass(frozen=True)
21
- class SentimentResult:
22
- label: str # "positive" | "neutral" | "negative" | "mixed" | "unknown"
23
- confidence: float # 0.0 .. 1.0
24
-
25
-
26
- class ChatBot:
27
- """
28
- Minimal chatbot that uses provider-agnostic sentiment via providers_unified.
29
- Public API:
30
- - reply(text: str) -> Dict[str, object]
31
- - capabilities() -> Dict[str, object]
32
- """
33
-
34
- def __init__(self, system_prompt: str = "You are a concise helper.") -> None:
35
- self._system_prompt = _trim(system_prompt, 800)
36
- # Expose which provider is intended/active (for diagnostics)
37
- self._mode = os.getenv("AI_PROVIDER") or "auto"
38
-
39
- def capabilities(self) -> Dict[str, object]:
40
- """List what this bot can do."""
41
- return {
42
- "system": "chatbot",
43
- "mode": self._mode, # "auto" or a pinned provider (hf/azure/openai/cohere/deepai/offline)
44
- "features": ["text-input", "sentiment-analysis", "help"],
45
- "commands": {"help": "Describe capabilities and usage."},
46
- }
47
-
48
- def reply(self, text: str) -> Dict[str, object]:
49
- """Produce a reply and sentiment for one user message."""
50
- user = _trim(text)
51
- if not user:
52
- return self._make_response(
53
- "I didn't catch that. Please provide some text.",
54
- SentimentResult("unknown", 0.0),
55
- )
56
-
57
- if user.lower() in {"help", "/help"}:
58
- return {"reply": self._format_help(), "capabilities": self.capabilities()}
59
-
60
- s = analyze_sentiment(user) # -> {"provider", "label", "score", ...}
61
- sr = SentimentResult(label=str(s.get("label", "neutral")), confidence=float(s.get("score", 0.5)))
62
- return self._make_response(self._compose(sr), sr)
63
-
64
- # ---- internals ----
65
-
66
- def _format_help(self) -> str:
67
- caps = self.capabilities()
68
- feats = ", ".join(caps["features"])
69
- return f"I can analyze sentiment and respond concisely. Features: {feats}. Send any text or type 'help'."
70
-
71
- @staticmethod
72
- def _make_response(reply: str, s: SentimentResult) -> Dict[str, object]:
73
- return {"reply": reply, "sentiment": s.label, "confidence": round(float(s.confidence), 2)}
74
-
75
- @staticmethod
76
- def _compose(s: SentimentResult) -> str:
77
- if s.label == "positive":
78
- return "Thanks for sharing. I detected a positive sentiment."
79
- if s.label == "negative":
80
- return "I hear your concern. I detected a negative sentiment."
81
- if s.label == "neutral":
82
- return "Noted. The sentiment appears neutral."
83
- if s.label == "mixed":
84
- return "Your message has mixed signals. Can you clarify?"
85
- return "I could not determine the sentiment. Please rephrase."
86
-
87
-
88
- # Optional: local REPL for quick manual testing
89
- def _interactive_loop() -> None:
90
- bot = ChatBot()
91
- try:
92
- while True:
93
- msg = input("> ").strip()
94
- if msg.lower() in {"exit", "quit"}:
95
- break
96
- print(json.dumps(bot.reply(msg), ensure_ascii=False))
97
- except (EOFError, KeyboardInterrupt):
98
- pass
99
-
100
-
101
- if __name__ == "__main__":
102
- _interactive_loop()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
full/agenticcore/providers_unified.py DELETED
@@ -1,273 +0,0 @@
1
- """
2
- providers_unified.py
3
- Unified, switchable providers for sentiment + (optional) text generation.
4
- Selection order unless AI_PROVIDER is set:
5
- HF -> AZURE -> OPENAI -> COHERE -> DEEPAI -> OFFLINE
6
- Env vars:
7
- HF_API_KEY
8
- MICROSOFT_AI_SERVICE_ENDPOINT, MICROSOFT_AI_API_KEY
9
- OPENAI_API_KEY, OPENAI_MODEL=gpt-3.5-turbo
10
- COHERE_API_KEY, COHERE_MODEL=command
11
- DEEPAI_API_KEY
12
- AI_PROVIDER = hf|azure|openai|cohere|deepai|offline
13
- HTTP_TIMEOUT = 20
14
- """
15
- from __future__ import annotations
16
- import os, json
17
- from typing import Dict, Any, Optional
18
- import requests
19
-
20
- TIMEOUT = float(os.getenv("HTTP_TIMEOUT", "20"))
21
-
22
- def _env(name: str, default: Optional[str] = None) -> Optional[str]:
23
- v = os.getenv(name)
24
- return v if (v is not None and str(v).strip() != "") else default
25
-
26
- def _pick_provider() -> str:
27
- forced = _env("AI_PROVIDER")
28
- if forced in {"hf", "azure", "openai", "cohere", "deepai", "offline"}:
29
- return forced
30
- if _env("HF_API_KEY"): return "hf"
31
- if _env("MICROSOFT_AI_API_KEY") and _env("MICROSOFT_AI_SERVICE_ENDPOINT"): return "azure"
32
- if _env("OPENAI_API_KEY"): return "openai"
33
- if _env("COHERE_API_KEY"): return "cohere"
34
- if _env("DEEPAI_API_KEY"): return "deepai"
35
- return "offline"
36
-
37
- # ---------------------------
38
- # Sentiment
39
- # ---------------------------
40
-
41
- def analyze_sentiment(text: str) -> Dict[str, Any]:
42
- provider = _pick_provider()
43
- try:
44
- if provider == "hf": return _sentiment_hf(text)
45
- if provider == "azure": return _sentiment_azure(text)
46
- if provider == "openai": return _sentiment_openai_prompt(text)
47
- if provider == "cohere": return _sentiment_cohere_prompt(text)
48
- if provider == "deepai": return _sentiment_deepai(text)
49
- return _sentiment_offline(text)
50
- except Exception as e:
51
- return {"provider": provider, "label": "neutral", "score": 0.5, "error": str(e)}
52
-
53
- def _sentiment_offline(text: str) -> Dict[str, Any]:
54
- t = (text or "").lower()
55
- pos = any(w in t for w in ["love","great","good","awesome","fantastic","thank","excellent","amazing"])
56
- neg = any(w in t for w in ["hate","bad","terrible","awful","worst","angry","horrible"])
57
- label = "positive" if pos and not neg else "negative" if neg and not pos else "neutral"
58
- score = 0.9 if label != "neutral" else 0.5
59
- return {"provider": "offline", "label": label, "score": score}
60
-
61
- def _sentiment_hf(text: str) -> Dict[str, Any]:
62
- """
63
- Hugging Face Inference API for sentiment.
64
- Uses canonical repo id and handles 404/401 and various payload shapes.
65
- """
66
- key = _env("HF_API_KEY")
67
- if not key:
68
- return _sentiment_offline(text)
69
-
70
- # canonical repo id to avoid 404
71
- model = _env("HF_MODEL_SENTIMENT", "distilbert/distilbert-base-uncased-finetuned-sst-2-english")
72
- timeout = int(_env("HTTP_TIMEOUT", "30"))
73
-
74
- headers = {
75
- "Authorization": f"Bearer {key}",
76
- "x-wait-for-model": "true",
77
- "Accept": "application/json",
78
- "Content-Type": "application/json",
79
- }
80
-
81
- r = requests.post(
82
- f"https://api-inference.huggingface.co/models/{model}",
83
- headers=headers,
84
- json={"inputs": text},
85
- timeout=timeout,
86
- )
87
-
88
- if r.status_code != 200:
89
- return {"provider": "hf", "label": "neutral", "score": 0.5, "error": f"HTTP {r.status_code}: {r.text[:500]}"}
90
-
91
- try:
92
- data = r.json()
93
- except Exception as e:
94
- return {"provider": "hf", "label": "neutral", "score": 0.5, "error": str(e)}
95
-
96
- if isinstance(data, dict) and "error" in data:
97
- return {"provider": "hf", "label": "neutral", "score": 0.5, "error": data["error"]}
98
-
99
- # normalize list shape
100
- arr = data[0] if isinstance(data, list) and data and isinstance(data[0], list) else (data if isinstance(data, list) else [])
101
- if not (isinstance(arr, list) and arr):
102
- return {"provider": "hf", "label": "neutral", "score": 0.5, "error": f"Unexpected payload: {data}"}
103
-
104
- top = max(arr, key=lambda x: x.get("score", 0.0) if isinstance(x, dict) else 0.0)
105
- raw = str(top.get("label", "")).upper()
106
- score = float(top.get("score", 0.5))
107
-
108
- mapping = {
109
- "LABEL_0": "negative", "LABEL_1": "neutral", "LABEL_2": "positive",
110
- "NEGATIVE": "negative", "NEUTRAL": "neutral", "POSITIVE": "positive",
111
- }
112
- label = mapping.get(raw, (raw.lower() or "neutral"))
113
-
114
- neutral_floor = float(os.getenv("SENTIMENT_NEUTRAL_THRESHOLD", "0.65"))
115
- if label in {"positive", "negative"} and score < neutral_floor:
116
- label = "neutral"
117
-
118
- return {"provider": "hf", "label": label, "score": score}
119
-
120
- def _sentiment_azure(text: str) -> Dict[str, Any]:
121
- try:
122
- from azure.core.credentials import AzureKeyCredential # type: ignore
123
- from azure.ai.textanalytics import TextAnalyticsClient # type: ignore
124
- except Exception:
125
- return _sentiment_offline(text)
126
- endpoint = _env("MICROSOFT_AI_SERVICE_ENDPOINT")
127
- key = _env("MICROSOFT_AI_API_KEY")
128
- if not (endpoint and key): return _sentiment_offline(text)
129
- client = TextAnalyticsClient(endpoint=endpoint.strip(), credential=AzureKeyCredential(key.strip()))
130
- resp = client.analyze_sentiment(documents=[text], show_opinion_mining=False)[0]
131
- scores = {
132
- "positive": float(getattr(resp.confidence_scores, "positive", 0.0) or 0.0),
133
- "neutral": float(getattr(resp.confidence_scores, "neutral", 0.0) or 0.0),
134
- "negative": float(getattr(resp.confidence_scores, "negative", 0.0) or 0.0),
135
- }
136
- label = max(scores, key=scores.get)
137
- return {"provider": "azure", "label": label, "score": scores[label]}
138
-
139
- def _sentiment_openai_prompt(text: str) -> Dict[str, Any]:
140
- key = _env("OPENAI_API_KEY")
141
- model = _env("OPENAI_MODEL", "gpt-3.5-turbo")
142
- if not key: return _sentiment_offline(text)
143
- url = "https://api.openai.com/v1/chat/completions"
144
- prompt = f"Classify the sentiment of this text as positive, negative, or neutral. Reply JSON with keys label and score (0..1). Text: {text!r}"
145
- r = requests.post(
146
- url,
147
- headers={"Authorization": f"Bearer {key}", "Content-Type": "application/json"},
148
- json={"model": model, "messages": [{"role": "user", "content": prompt}], "temperature": 0},
149
- timeout=TIMEOUT,
150
- )
151
- r.raise_for_status()
152
- content = r.json()["choices"][0]["message"]["content"]
153
- try:
154
- obj = json.loads(content)
155
- label = str(obj.get("label", "neutral")).lower()
156
- score = float(obj.get("score", 0.5))
157
- return {"provider": "openai", "label": label, "score": score}
158
- except Exception:
159
- l = "positive" if "positive" in content.lower() else "negative" if "negative" in content.lower() else "neutral"
160
- return {"provider": "openai", "label": l, "score": 0.5}
161
-
162
- def _sentiment_cohere_prompt(text: str) -> Dict[str, Any]:
163
- key = _env("COHERE_API_KEY")
164
- model = _env("COHERE_MODEL", "command")
165
- if not key: return _sentiment_offline(text)
166
- url = "https://api.cohere.ai/v1/generate"
167
- prompt = f"Classify the sentiment (positive, negative, neutral) and return JSON with keys label and score (0..1). Text: {text!r}"
168
- r = requests.post(
169
- url,
170
- headers={
171
- "Authorization": f"Bearer {key}",
172
- "Content-Type": "application/json",
173
- "Cohere-Version": "2022-12-06",
174
- },
175
- json={"model": model, "prompt": prompt, "max_tokens": 30, "temperature": 0},
176
- timeout=TIMEOUT,
177
- )
178
- r.raise_for_status()
179
- gen = (r.json().get("generations") or [{}])[0].get("text", "")
180
- try:
181
- obj = json.loads(gen)
182
- label = str(obj.get("label", "neutral")).lower()
183
- score = float(obj.get("score", 0.5))
184
- return {"provider": "cohere", "label": label, "score": score}
185
- except Exception:
186
- l = "positive" if "positive" in gen.lower() else "negative" if "negative" in gen.lower() else "neutral"
187
- return {"provider": "cohere", "label": l, "score": 0.5}
188
-
189
- def _sentiment_deepai(text: str) -> Dict[str, Any]:
190
- key = _env("DEEPAI_API_KEY")
191
- if not key: return _sentiment_offline(text)
192
- url = "https://api.deepai.org/api/sentiment-analysis"
193
- r = requests.post(url, headers={"api-key": key}, data={"text": text}, timeout=TIMEOUT)
194
- r.raise_for_status()
195
- data = r.json()
196
- label = (data.get("output") or ["neutral"])[0].lower()
197
- return {"provider": "deepai", "label": label, "score": 0.5 if label == "neutral" else 0.9}
198
-
199
- # ---------------------------
200
- # Text generation (optional)
201
- # ---------------------------
202
-
203
- def generate_text(prompt: str, max_tokens: int = 128) -> Dict[str, Any]:
204
- provider = _pick_provider()
205
- try:
206
- if provider == "hf": return _gen_hf(prompt, max_tokens)
207
- if provider == "openai": return _gen_openai(prompt, max_tokens)
208
- if provider == "cohere": return _gen_cohere(prompt, max_tokens)
209
- if provider == "deepai": return _gen_deepai(prompt, max_tokens)
210
- return {"provider": "offline", "text": f"(offline) {prompt[:160]}"}
211
- except Exception as e:
212
- return {"provider": provider, "text": f"(error) {str(e)}"}
213
-
214
- def _gen_hf(prompt: str, max_tokens: int) -> Dict[str, Any]:
215
- key = _env("HF_API_KEY")
216
- if not key: return {"provider": "offline", "text": f"(offline) {prompt[:160]}"}
217
- model = _env("HF_MODEL_GENERATION", "tiiuae/falcon-7b-instruct")
218
- r = requests.post(
219
- f"https://api-inference.huggingface.co/models/{model}",
220
- headers={"Authorization": f"Bearer {key}"},
221
- json={"inputs": prompt, "parameters": {"max_new_tokens": max_tokens}},
222
- timeout=TIMEOUT,
223
- )
224
- r.raise_for_status()
225
- data = r.json()
226
- if isinstance(data, list) and data and "generated_text" in data[0]:
227
- return {"provider": "hf", "text": data[0]["generated_text"]}
228
- return {"provider": "hf", "text": str(data)}
229
-
230
- def _gen_openai(prompt: str, max_tokens: int) -> Dict[str, Any]:
231
- key = _env("OPENAI_API_KEY")
232
- model = _env("OPENAI_MODEL", "gpt-3.5-turbo")
233
- if not key: return {"provider": "offline", "text": f"(offline) {prompt[:160]}"}
234
- url = "https://api.openai.com/v1/chat/completions"
235
- r = requests.post(
236
- url,
237
- headers={"Authorization": f"Bearer {key}", "Content-Type": "application/json"},
238
- json={"model": model, "messages": [{"role": "user", "content": prompt}], "max_tokens": max_tokens},
239
- timeout=TIMEOUT,
240
- )
241
- r.raise_for_status()
242
- data = r.json()
243
- text = data["choices"][0]["message"]["content"]
244
- return {"provider": "openai", "text": text}
245
-
246
- def _gen_cohere(prompt: str, max_tokens: int) -> Dict[str, Any]:
247
- key = _env("COHERE_API_KEY")
248
- model = _env("COHERE_MODEL", "command")
249
- if not key: return {"provider": "offline", "text": f"(offline) {prompt[:160]}"}
250
- url = "https://api.cohere.ai/v1/generate"
251
- r = requests.post(
252
- url,
253
- headers={
254
- "Authorization": f"Bearer {key}",
255
- "Content-Type": "application/json",
256
- "Cohere-Version": "2022-12-06",
257
- },
258
- json={"model": model, "prompt": prompt, "max_tokens": max_tokens},
259
- timeout=TIMEOUT,
260
- )
261
- r.raise_for_status()
262
- data = r.json()
263
- text = data.get("generations", [{}])[0].get("text", "")
264
- return {"provider": "cohere", "text": text}
265
-
266
- def _gen_deepai(prompt: str, max_tokens: int) -> Dict[str, Any]:
267
- key = _env("DEEPAI_API_KEY")
268
- if not key: return {"provider": "offline", "text": f"(offline) {prompt[:160]}"}
269
- url = "https://api.deepai.org/api/text-generator"
270
- r = requests.post(url, headers={"api-key": key}, data={"text": prompt}, timeout=TIMEOUT)
271
- r.raise_for_status()
272
- data = r.json()
273
- return {"provider": "deepai", "text": data.get("output", "")}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
full/backend/__init__.py DELETED
File without changes
full/backend/app/__init__.py DELETED
File without changes
full/backend/app/main.py DELETED
@@ -1,69 +0,0 @@
1
- # backend/app/main.py
2
- from __future__ import annotations
3
-
4
- import os
5
- import time
6
- import traceback
7
- from pathlib import Path
8
-
9
- from fastapi import FastAPI
10
- from fastapi.middleware.cors import CORSMiddleware
11
- from fastapi.responses import HTMLResponse
12
- from fastapi.staticfiles import StaticFiles
13
-
14
-
15
- def create_app() -> FastAPI:
16
- app = FastAPI(title="AgenticCore Backend")
17
-
18
- # --- CORS: allow local dev origins + file:// (appears as 'null') ---
19
- app.add_middleware(
20
- CORSMiddleware,
21
- allow_origins=[
22
- "http://127.0.0.1:3000", "http://localhost:3000",
23
- "http://127.0.0.1:5173", "http://localhost:5173",
24
- "http://127.0.0.1:8080", "http://localhost:8080",
25
- "http://127.0.0.1:8000", "http://localhost:8000",
26
- "null", # file:// pages in some browsers
27
- ],
28
- allow_credentials=False,
29
- allow_methods=["*"],
30
- allow_headers=["*"],
31
- )
32
-
33
- @app.get("/health")
34
- def health():
35
- return {"ok": True, "version": "0.3.0", "time": int(time.time())}
36
-
37
- @app.get("/status")
38
- def status():
39
- provider = os.getenv("AI_PROVIDER") or "auto"
40
- return {"provider": provider}
41
-
42
- # --- Routers ---
43
- try:
44
- from backend.app.routers.chatbot import router as chatbot_router
45
- app.include_router(chatbot_router) # exposes POST /chatbot/message
46
- print("✅ Chatbot router mounted at /chatbot")
47
- except Exception as e:
48
- print("❌ Failed to mount chatbot router:", e)
49
- traceback.print_exc()
50
-
51
- # --- Static frontends served by FastAPI (same-origin -> no CORS) ---
52
- FRONTEND_DIR = Path(__file__).resolve().parents[2] / "frontend"
53
- app.mount("/ui2", StaticFiles(directory=str(FRONTEND_DIR)), name="frontend")
54
-
55
- # Keep your existing single-file UI at /ui (optional)
56
- FRONTEND_FILE = FRONTEND_DIR / "agenticcore_frontend.html"
57
-
58
- @app.get("/ui", response_class=HTMLResponse)
59
- def ui():
60
- try:
61
- return FRONTEND_FILE.read_text(encoding="utf-8")
62
- except Exception:
63
- return HTMLResponse("<h1>UI not found</h1>", status_code=404)
64
-
65
- return app
66
-
67
-
68
- # Uvicorn entrypoint
69
- app = create_app()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
full/backend/app/routers/__init__.py DELETED
File without changes
full/backend/app/routers/chatbot.py DELETED
@@ -1,27 +0,0 @@
1
- from fastapi import APIRouter
2
- from pydantic import BaseModel
3
- from typing import Optional, Dict, Any
4
-
5
- router = APIRouter(prefix="/chatbot", tags=["chatbot"])
6
-
7
- class ChatIn(BaseModel):
8
- message: str
9
- thread: Optional[str] = None
10
-
11
- class ChatOut(BaseModel):
12
- reply: str
13
- sentiment: Optional[str] = None
14
- confidence: Optional[float] = None
15
- thread: Optional[str] = None
16
-
17
- @router.post("/message", response_model=ChatOut)
18
- def post_message(body: ChatIn) -> Dict[str, Any]:
19
- from agenticcore.chatbot.services import ChatBot
20
- bot = ChatBot()
21
- res = bot.reply(body.message)
22
- return {
23
- "reply": res.get("reply", ""),
24
- "sentiment": res.get("sentiment"),
25
- "confidence": res.get("confidence"),
26
- "thread": body.thread or res.get("thread"),
27
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
full/extras/__init__.py DELETED
File without changes
full/frontend/__init__.py DELETED
File without changes
full/frontend/agenticcore_frontend.html DELETED
@@ -1,200 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1" />
6
- <title>AgenticCore Chatbot Frontend</title>
7
- <style>
8
- :root {
9
- --bg: #0b0d12;
10
- --panel: #0f172a;
11
- --panel-2: #111827;
12
- --text: #e5e7eb;
13
- --muted: #9ca3af;
14
- --accent: #60a5fa;
15
- --border: #1f2940;
16
- --danger: #ef4444;
17
- --success: #22c55e;
18
- }
19
- * { box-sizing: border-box; }
20
- body { margin: 0; font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; background: var(--bg); color: var(--text); }
21
- .wrap { max-width: 920px; margin: 32px auto; padding: 0 16px; }
22
- header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px; gap: 16px; }
23
- header h1 { font-size: 18px; margin: 0; letter-spacing: .3px; }
24
- header .badge { font-size: 12px; opacity: .85; padding: 4px 8px; border:1px solid var(--border); border-radius: 999px; background: rgba(255,255,255,0.03); }
25
- .card { background: var(--panel); border: 1px solid var(--border); border-radius: 16px; padding: 16px; }
26
- .row { display: flex; gap: 10px; align-items: center; }
27
- .stack { display: grid; gap: 12px; }
28
- label { font-size: 12px; color: var(--muted); }
29
- input[type=text] { flex: 1; padding: 12px 14px; border-radius: 12px; border: 1px solid var(--border); background: var(--panel-2); color: var(--text); outline: none; }
30
- input[type=text]::placeholder { color: #6b7280; }
31
- button { padding: 10px 14px; border-radius: 12px; border: 1px solid var(--border); background: #1f2937; color: var(--text); cursor: pointer; transition: transform .02s ease, background .2s; }
32
- button:hover { background: #273449; }
33
- button:active { transform: translateY(1px); }
34
- .btn-primary { background: #1f2937; border-color: #31405a; }
35
- .btn-ghost { background: transparent; border-color: var(--border); }
36
- .grid { display: grid; gap: 12px; }
37
- .grid-2 { grid-template-columns: 1fr 1fr; }
38
- .log { margin-top: 16px; display: grid; gap: 10px; }
39
- .bubble { max-width: 80%; padding: 12px 14px; border-radius: 14px; line-height: 1.35; }
40
- .user { background: #1e293b; border:1px solid #2b3b55; margin-left: auto; border-bottom-right-radius: 4px; }
41
- .bot { background: #0d1b2a; border:1px solid #223049; margin-right: auto; border-bottom-left-radius: 4px; }
42
- .meta { font-size: 12px; color: var(--muted); margin-top: 4px; }
43
- pre { margin: 0; white-space: pre-wrap; word-break: break-word; }
44
- .status { display:flex; align-items:center; gap:8px; font-size: 12px; color: var(--muted); }
45
- .dot { width:8px; height:8px; border-radius:999px; background: #64748b; display:inline-block; }
46
- .dot.ok { background: var(--success); }
47
- .dot.bad { background: var(--danger); }
48
- footer { margin: 24px 0; text-align:center; color: var(--muted); font-size: 12px; }
49
- .small { font-size: 12px; }
50
- @media (max-width: 700px) { .grid-2 { grid-template-columns: 1fr; } }
51
- </style>
52
- </head>
53
- <body>
54
- <div class="wrap">
55
- <header>
56
- <h1>AgenticCore Chatbot Frontend</h1>
57
- <div class="badge">Frontend → FastAPI → providers_unified</div>
58
- </header>
59
-
60
- <section class="card stack">
61
- <div class="grid grid-2">
62
- <div class="stack">
63
- <label for="backend">Backend URL</label>
64
- <div class="row">
65
- <input id="backend" type="text" placeholder="http://127.0.0.1:8000" />
66
- <button id="save" class="btn-ghost">Save</button>
67
- </div>
68
- <div class="status" id="status"><span class="dot"></span><span>Not checked</span></div>
69
- </div>
70
- <div class="stack">
71
- <label for="message">Message</label>
72
- <div class="row">
73
- <input id="message" type="text" placeholder="Type a message…" />
74
- <button id="send" class="btn-primary">Send</button>
75
- </div>
76
- <div class="row">
77
- <button id="cap" class="btn-ghost small">Capabilities</button>
78
- <button id="health" class="btn-ghost small">Health</button>
79
- <button id="clear" class="btn-ghost small">Clear</button>
80
- </div>
81
- </div>
82
- </div>
83
- <div class="log" id="log"></div>
84
- </section>
85
-
86
- <footer>
87
- Use with your FastAPI backend at <code>/chatbot/message</code>. Configure CORS if you serve this file from a different origin.
88
- </footer>
89
- </div>
90
-
91
- <script>
92
- const $ = (sel) => document.querySelector(sel);
93
- const backendInput = $('#backend');
94
- const sendBtn = $('#send');
95
- const saveBtn = $('#save');
96
- const msgInput = $('#message');
97
- const capBtn = $('#cap');
98
- const healthBtn = $('#health');
99
- const clearBtn = $('#clear');
100
- const log = $('#log');
101
- const status = $('#status');
102
- const dot = status.querySelector('.dot');
103
- const statusText = status.querySelector('span:last-child');
104
-
105
- function getBackendUrl() {
106
- return localStorage.getItem('BACKEND_URL') || 'http://127.0.0.1:8000';
107
- }
108
- function setBackendUrl(v) {
109
- localStorage.setItem('BACKEND_URL', v);
110
- }
111
- function cardUser(text) {
112
- const div = document.createElement('div');
113
- div.className = 'bubble user';
114
- div.textContent = text;
115
- log.appendChild(div);
116
- log.scrollTop = log.scrollHeight;
117
- }
118
- function cardBot(obj) {
119
- const wrap = document.createElement('div');
120
- wrap.className = 'bubble bot';
121
- const pre = document.createElement('pre');
122
- pre.textContent = typeof obj === 'string' ? obj : JSON.stringify(obj, null, 2);
123
- wrap.appendChild(pre);
124
- log.appendChild(wrap);
125
- log.scrollTop = log.scrollHeight;
126
- }
127
- function setStatus(ok, text) {
128
- dot.classList.toggle('ok', !!ok);
129
- dot.classList.toggle('bad', ok === false);
130
- statusText.textContent = text || (ok ? 'OK' : 'Error');
131
- }
132
- async function api(path, init) {
133
- const base = backendInput.value.trim().replace(/\/$/, '');
134
- const url = base + path;
135
- const resp = await fetch(url, init);
136
- if (!resp.ok) {
137
- let t = await resp.text().catch(() => '');
138
- throw new Error(`HTTP ${resp.status} ${resp.statusText} — ${t}`);
139
- }
140
- const contentType = resp.headers.get('content-type') || '';
141
- if (contentType.includes('application/json')) return resp.json();
142
- return resp.text();
143
- }
144
-
145
- async function checkHealth() {
146
- try {
147
- const h = await api('/health', { method: 'GET' });
148
- setStatus(true, 'Healthy');
149
- cardBot({ health: h });
150
- } catch (e) {
151
- setStatus(false, String(e.message || e));
152
- cardBot({ error: String(e.message || e) });
153
- }
154
- }
155
-
156
- async function sendMessage() {
157
- const text = msgInput.value.trim();
158
- if (!text) return;
159
- cardUser(text);
160
- msgInput.value = '';
161
- try {
162
- const data = await api('/chatbot/message', {
163
- method: 'POST',
164
- headers: { 'Content-Type': 'application/json' },
165
- body: JSON.stringify({ message: text })
166
- });
167
- cardBot(data);
168
- } catch (e) {
169
- cardBot({ error: String(e.message || e) });
170
- }
171
- }
172
-
173
- async function showCapabilities() {
174
- try {
175
- // Prefer API if available; if 404, fall back to library-like prompt.
176
- const data = await api('/chatbot/message', {
177
- method: 'POST',
178
- headers: { 'Content-Type': 'application/json' },
179
- body: JSON.stringify({ message: 'help' })
180
- });
181
- cardBot(data);
182
- } catch (e) {
183
- cardBot({ capabilities: ['text-input','sentiment-analysis','help'], note: 'API help failed, showing defaults', error: String(e.message || e) });
184
- }
185
- }
186
-
187
- // Wire up
188
- backendInput.value = getBackendUrl();
189
- saveBtn.onclick = () => { setBackendUrl(backendInput.value.trim()); setStatus(null, 'Saved'); };
190
- sendBtn.onclick = sendMessage;
191
- msgInput.addEventListener('keydown', (ev) => { if (ev.key === 'Enter') sendMessage(); });
192
- capBtn.onclick = showCapabilities;
193
- healthBtn.onclick = checkHealth;
194
- clearBtn.onclick = () => { log.innerHTML = ''; setStatus(null, 'Idle'); };
195
-
196
- // Initial health ping
197
- checkHealth();
198
- </script>
199
- </body>
200
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
full/frontend/chat_console.html DELETED
@@ -1,77 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="utf-8" />
5
- <title>Console Chat Tester</title>
6
- <meta name="viewport" content="width=device-width, initial-scale=1" />
7
- <style>
8
- body{ font-family: ui-sans-serif, system-ui, Arial; margin:20px; }
9
- .row{ display:flex; gap:8px; align-items:center; margin:6px 0; }
10
- input[type=text]{ flex:1; padding:8px; }
11
- button{ padding:8px 10px; }
12
- pre{ background:#0b1020; color:#d6e7ff; padding:10px; height:320px; overflow:auto; }
13
- .chip{ display:inline-block; padding:3px 8px; background:#eef; border-radius:12px; margin-left:8px; }
14
- </style>
15
- </head>
16
- <body>
17
- <h2>AgenticCore Console</h2>
18
-
19
- <div class="row">
20
- <label>Backend</label>
21
- <input id="base" type="text" value="http://127.0.0.1:8000" />
22
- <button id="btnHealth">Health</button>
23
- <button id="btnRoutes">Routes</button>
24
- </div>
25
-
26
- <div class="row">
27
- <input id="msg" type="text" placeholder="Say something…" />
28
- <button id="btnSend">POST /chatbot/message</button>
29
- </div>
30
-
31
- <div>
32
- <span>Mode:</span>
33
- <span id="mode" class="chip">API</span>
34
- </div>
35
-
36
- <pre id="out"></pre>
37
-
38
- <script>
39
- const $ = id => document.getElementById(id);
40
- const out = $("out");
41
- function print(o){ out.textContent += (typeof o==="string" ? o : JSON.stringify(o,null,2)) + "\n"; out.scrollTop = out.scrollHeight; }
42
- function join(b, p){ return b.replace(/\/+$/,"") + p; }
43
-
44
- async function health(){
45
- try{
46
- const r = await fetch(join($("base").value, "/health"));
47
- print(await r.json());
48
- }catch(e){ print("health error: " + e); }
49
- }
50
- async function routes(){
51
- try{
52
- const r = await fetch(join($("base").value, "/openapi.json"));
53
- const j = await r.json();
54
- print({ routes: Object.keys(j.paths) });
55
- }catch(e){ print("routes error: " + e); }
56
- }
57
- async function send(){
58
- const text = $("msg").value.trim();
59
- if(!text){ print("enter a message first"); return; }
60
- try{
61
- const r = await fetch(join($("base").value, "/chatbot/message"), {
62
- method:"POST",
63
- headers:{ "Content-Type":"application/json" },
64
- body: JSON.stringify({ message: text })
65
- });
66
- print(await r.json());
67
- }catch(e){ print("send error: " + e); }
68
- }
69
- $("btnHealth").onclick = health;
70
- $("btnRoutes").onclick = routes;
71
- $("btnSend").onclick = send;
72
-
73
- // boot
74
- health();
75
- </script>
76
- </body>
77
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
full/frontend/chat_minimal.html DELETED
@@ -1,89 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="utf-8" />
5
- <title>Minimal Chat Tester</title>
6
- <meta name="viewport" content="width=device-width, initial-scale=1" />
7
- <style>
8
- body { font-family: system-ui, Arial, sans-serif; margin: 24px; }
9
- .row { display:flex; gap:8px; align-items:center; margin-bottom:8px; }
10
- input[type=text]{ width:420px; padding:8px; }
11
- textarea{ width:100%; height:240px; padding:8px; }
12
- button{ padding:8px 12px; }
13
- .ok{ color:#1a7f37; }
14
- .warn{ color:#b54708; }
15
- .err{ color:#b42318; }
16
- </style>
17
- </head>
18
- <body>
19
- <h2>Minimal Chat Tester → FastAPI /chatbot/message</h2>
20
-
21
- <div class="row">
22
- <label>Backend URL:</label>
23
- <input id="base" type="text" value="http://127.0.0.1:8000" />
24
- <button id="btnHealth">Health</button>
25
- <button id="btnCaps">Capabilities</button>
26
- </div>
27
-
28
- <div class="row">
29
- <input id="msg" type="text" placeholder="Type a message…" />
30
- <button id="btnSend">Send</button>
31
- </div>
32
-
33
- <p id="status"></p>
34
- <textarea id="log" readonly></textarea>
35
-
36
- <script>
37
- const $ = id => document.getElementById(id);
38
- const log = (o, cls="") => {
39
- const line = (typeof o === "string") ? o : JSON.stringify(o, null, 2);
40
- $("log").value += line + "\n";
41
- $("log").scrollTop = $("log").scrollHeight;
42
- if(cls) { $("status").className = cls; $("status").textContent = line; }
43
- };
44
-
45
- function urlJoin(base, path) {
46
- return base.replace(/\/+$/,"") + path;
47
- }
48
-
49
- async function health() {
50
- try {
51
- const r = await fetch(urlJoin($("base").value, "/health"));
52
- const j = await r.json();
53
- log(j, "ok");
54
- } catch (e) { log("Health error: " + e, "err"); }
55
- }
56
-
57
- async function caps() {
58
- try {
59
- // Prefer library-like caps endpoint if you expose one; otherwise call /openapi.json for visibility
60
- const r = await fetch(urlJoin($("base").value, "/openapi.json"));
61
- const j = await r.json();
62
- log({paths: Object.keys(j.paths).slice(0,20)}, "ok");
63
- } catch (e) { log("Caps error: " + e, "err"); }
64
- }
65
-
66
- async function sendMsg() {
67
- const text = $("msg").value.trim();
68
- if(!text) { log("Please type a message.", "warn"); return; }
69
- try {
70
- const r = await fetch(urlJoin($("base").value, "/chatbot/message"), {
71
- method:"POST",
72
- headers:{ "Content-Type":"application/json" },
73
- body: JSON.stringify({ message: text })
74
- });
75
- if(!r.ok) throw new Error(`${r.status} ${r.statusText}`);
76
- const j = await r.json();
77
- log(j, "ok");
78
- } catch (e) { log("Send error: " + e, "err"); }
79
- }
80
-
81
- $("btnHealth").onclick = health;
82
- $("btnCaps").onclick = caps;
83
- $("btnSend").onclick = sendMsg;
84
-
85
- // Warmup
86
- health();
87
- </script>
88
- </body>
89
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
full/requirements.txt DELETED
@@ -1,14 +0,0 @@
1
- # Web
2
- fastapi>=0.112
3
- uvicorn[standard]>=0.30
4
- pydantic>=2.7
5
-
6
- # HTTP
7
- requests>=2.32
8
-
9
- # Optional Bot Framework demo
10
- aiohttp>=3.9
11
- botbuilder-core>=4.15
12
-
13
- # Dev
14
- pytest>=8.0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
full/tests/__init__.py DELETED
File without changes
full/tests/test_routes.py DELETED
@@ -1,6 +0,0 @@
1
- def test_routes_mount():
2
- from backend.app.main import create_app
3
- app = create_app()
4
- paths = [getattr(r, "path", "") for r in app.routes]
5
- assert "/chatbot/message" in paths
6
- assert "/health" in paths
 
 
 
 
 
 
 
full/tools/__init__.py DELETED
File without changes
full/tools/quick_sanity.py DELETED
@@ -1,13 +0,0 @@
1
- # tools/quick_sanity.py
2
- """
3
- Tiny sanity test for MBF helpers. Run from repo root or set PYTHONPATH.
4
- """
5
- import sys, os
6
- # Add repo root so 'mbf_bot' is importable if running directly
7
- sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
8
-
9
- from mbf_bot.skills import reverse_text, capabilities, normalize
10
-
11
- print("caps:", capabilities())
12
- print("reverse:", reverse_text("hello"))
13
- print("cmd:", normalize(" Help "))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
full/tools/smoke_test.py DELETED
@@ -1,11 +0,0 @@
1
- import os, json, requests
2
- from agenticcore.chatbot.services import ChatBot
3
-
4
- def p(title, data): print(f"\n== {title} ==\n{json.dumps(data, indent=2)}")
5
-
6
- bot = ChatBot()
7
- p("Lib/Direct", bot.reply("I really love this"))
8
-
9
- url = os.getenv("BACKEND_URL", "http://127.0.0.1:8000")
10
- r = requests.get(f"{url}/health"); p("API/Health", r.json())
11
- r = requests.post(f"{url}/chatbot/message", json={"message":"api path test"}); p("API/Chat", r.json())