Add MonAI Project to TinyFish CookBook#227
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughThis PR adds the MonAI cookbook recipe: a FastAPI backend using TinyFish and optional OpenAI analysis for trend, menu-gap, supplier, and RFQ workflows, plus a React/Vite/TanStack Router frontend with landing and dashboard experiences, shared UI primitives, deployment/test config, and docs. ChangesMonAI recipe: backend + frontend + docs
Estimated code review effort: 4 (Complex) | ~75 minutes Sequence Diagram(s)sequenceDiagram
participant Client
participant AnalysisRoute
participant TinyFishClient
participant AIClient
participant ReportBuilder
Client->>AnalysisRoute: POST /api/analysis/menu-gap
AnalysisRoute->>TinyFishClient: search_tinyfish(trends query)
AnalysisRoute->>TinyFishClient: fetch_tinyfish(competitor URLs)
AnalysisRoute->>AIClient: analyze_data(prompt) [if has_openai]
AIClient-->>AnalysisRoute: raw JSON text
AnalysisRoute->>ReportBuilder: build_menu_gap_report(analysis)
ReportBuilder-->>AnalysisRoute: report
AnalysisRoute-->>Client: menu_gap_analysis + report
sequenceDiagram
participant User
participant AnalysisPanel
participant api_ts as api.ts
participant FastAPIBackend
participant normalizeResult
participant buildClientReport
User->>AnalysisPanel: submit analysis form
AnalysisPanel->>api_ts: runMenuGapAnalysis / runTrendForecast / ...
api_ts->>FastAPIBackend: POST or GET analysis endpoint
FastAPIBackend-->>api_ts: analysis payload + report
api_ts-->>AnalysisPanel: typed result
AnalysisPanel->>normalizeResult: resolveReport(result)
normalizeResult->>buildClientReport: extract supported report shape
buildClientReport-->>AnalysisPanel: IntelligenceReport or null
AnalysisPanel-->>User: render report or preview
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Contribute MonAI as a cookbook recipe: Vietnam F&B trend intelligence using TinyFish Search and Fetch with a FastAPI backend and React dashboard.
8d23d3e to
887c971
Compare
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 10
🧹 Nitpick comments (13)
monai/frontend/src/components/ui/radio-group.tsx (1)
7-34: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueStandard shadcn pattern, works fine.
Minor note: React 19 makes
refa regular prop, soReact.forwardRefis no longer required, though still supported. Not worth refactoring this generated boilerplate now.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@monai/frontend/src/components/ui/radio-group.tsx` around lines 7 - 34, No code change is needed in RadioGroup or RadioGroupItem: the current React.forwardRef pattern in the radio-group component is acceptable and should be left as generated boilerplate. Keep the existing RadioGroupPrimitive.Root and RadioGroupPrimitive.Item wrappers unchanged unless there is a separate functional issue to address.monai/frontend/tsconfig.json (1)
2-22: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winEnable
strictmode.
strictis absent fromcompilerOptions, so TypeScript defaults to non-strict (nostrictNullChecks,noImplicitAny, etc.) across the whole frontend. Standard Vite React+TS scaffolds always set"strict": true"; without it, null/undefined and implicit-any bugs across ~40 new component files won't be caught at compile time.🛠️ Proposed fix
"compilerOptions": { "target": "es2023", "module": "esnext", "lib": ["ES2023", "DOM"], "types": ["vite/client"], "allowArbitraryExtensions": true, "skipLibCheck": true, + "strict": true, /* Bundler mode */🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@monai/frontend/tsconfig.json` around lines 2 - 22, Enable TypeScript strict mode in the frontend tsconfig by adding the strict compiler option inside compilerOptions. Update the monai/frontend/tsconfig.json configuration so the existing TypeScript setup used by the Vite frontend is running with strict type checking, and keep the change localized to the tsconfig compilerOptions block.monai/frontend/src/components/ui/toggle-group.tsx (1)
8-11: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winItem-level
variant/sizeoverrides can never take effect.The context default (
{ size: "default", variant: "default" }) is always truthy, socontext.variant || variant/context.size || sizealways resolve to the context value — an individualToggleGroupItem's ownvariant/sizeprop is silently ignored in every case.🛠️ Proposed fix
-const ToggleGroupContext = React.createContext<VariantProps<typeof toggleVariants>>({ - size: "default", - variant: "default", -}); +const ToggleGroupContext = React.createContext<VariantProps<typeof toggleVariants>>({ + size: undefined, + variant: undefined, +});<ToggleGroupPrimitive.Item ref={ref} className={cn( toggleVariants({ - variant: context.variant || variant, - size: context.size || size, + variant: variant || context.variant, + size: size || context.size, }),Also applies to: 40-43
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@monai/frontend/src/components/ui/toggle-group.tsx` around lines 8 - 11, The ToggleGroupContext default values are forcing every ToggleGroupItem to use the context’s size and variant, so item-level overrides never win. Update the ToggleGroupContext and the ToggleGroupItem prop resolution so an item can supply its own variant/size when present, instead of always falling back to the default context values. Focus on the ToggleGroupContext definition and the item rendering logic that reads context.variant and context.size.monai/frontend/src/components/monai/VietnamMap.tsx (1)
3-9: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueContradictory a11y attributes.
aria-hidden="true"combined withrole="img"and a<title>is contradictory:aria-hiddenremoves the element (and its title) from the accessibility tree entirely, so the descriptive title/role never reach assistive tech. If purely decorative, droprole/<title>; if meaningful, droparia-hidden.♻️ Optional fix (decorative-only)
<svg viewBox="0 0 200 360" className="mx-auto h-32 w-auto" aria-hidden="true" - role="img" > - <title>Map of Vietnam</title>🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@monai/frontend/src/components/monai/VietnamMap.tsx` around lines 3 - 9, The SVG in VietnamMap has conflicting accessibility attributes: aria-hidden="true" prevents the element from being exposed to assistive tech, so its role="img" and <title> are ineffective. Update the svg markup in VietnamMap to choose one path: if the map is decorative, remove role and <title>; if it should be announced, remove aria-hidden and keep the descriptive title/role.monai/frontend/src/components/ui/progress.tsx (1)
15-18: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low valueConsider clamping
valueto [0, 100].
translateX(-${100 - (value || 0)}%)doesn't clampvalue; a value >100 or negative produces an overflowing/inverted bar. Low priority since it's boilerplate and callers likely pass valid percentages.♻️ Optional clamp
- style={{ transform: `translateX(-${100 - (value || 0)}%)` }} + style={{ transform: `translateX(-${100 - Math.min(100, Math.max(0, value || 0))}%)` }}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@monai/frontend/src/components/ui/progress.tsx` around lines 15 - 18, Clamp the progress value used in ProgressPrimitive.Indicator so the translateX calculation stays within 0–100; in the progress.tsx component, update the style logic that currently uses value || 0 to normalize out-of-range inputs before computing the transform. Keep the change localized to the Progress component so callers can still pass raw values while the bar rendering remains stable for negative or >100 percentages.monai/frontend/src/components/monai/analysisExamples.ts (1)
284-291: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicate
reportunwrap logic.
extractReporthere duplicates the samerecord.reportcheck already implemented inbuildClientReport(monai/frontend/src/lib/buildClientReport.ts, lines 15-17). Consider havingbuildClientReportdelegate toextractReport(or vice versa) to keep the unwrap logic in one place.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@monai/frontend/src/components/monai/analysisExamples.ts` around lines 284 - 291, The report unwrapping logic is duplicated between extractReport and buildClientReport, so consolidate it into a single helper and have the other function delegate to it. Update buildClientReport to call extractReport, or move the shared record.report extraction into buildClientReport and reuse it from extractReport, keeping the IntelligenceReport handling in one place and preserving the same null behavior.monai/frontend/src/components/monai/ProblemTicker.tsx (1)
3-28: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winTicker duplicates content for screen readers.
Items are duplicated for the scroll animation but nothing marks the ticker as decorative, so assistive tech will announce every source name twice.
♿ Proposed fix
- <div className="overflow-hidden"> - <div className="ticker-track gap-8"> + <div className="overflow-hidden" aria-hidden="true"> + <div className="ticker-track gap-8">🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@monai/frontend/src/components/monai/ProblemTicker.tsx` around lines 3 - 28, The ticker in ProblemTicker duplicates its items for animation, which causes screen readers to announce the same content twice. Update the ProblemTicker component so the repeated ticker track is treated as decorative by assistive tech, using the existing section/div/span structure and the ticker-track/items rendering as the place to apply the fix. Ensure only one accessible announcement remains by hiding the duplicated scrolling content from screen readers while keeping the visual animation unchanged.monai/frontend/src/lib/api.ts (1)
12-46: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winNo request timeout on API calls.
apiFetchhas no timeout, so a slow/hanging backend (e.g. Render cold start) leaves the caller waiting indefinitely with no way to recover except a page reload.⏱️ Proposed fix using AbortController
async function apiFetch<T>(path: string, init?: RequestInit): Promise<T> { const base = getApiBase(); if (base === null) { throw new Error("API is not configured. Set VITE_API_BASE_URL or run the dev server with backend."); } - const res = await fetch(`${base}${path}`, { - ...init, - headers: { - "Content-Type": "application/json", - ...init?.headers, - }, - }); + const controller = new AbortController(); + const timeoutId = window.setTimeout(() => controller.abort(), 30000); + let res: Response; + try { + res = await fetch(`${base}${path}`, { + ...init, + signal: controller.signal, + headers: { + "Content-Type": "application/json", + ...init?.headers, + }, + }); + } catch (err) { + if (err instanceof DOMException && err.name === "AbortError") { + throw new Error(`Request to ${base}${path} timed out.`); + } + throw err; + } finally { + window.clearTimeout(timeoutId); + }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@monai/frontend/src/lib/api.ts` around lines 12 - 46, The apiFetch helper currently waits forever on slow or hanging requests because it does not enforce a timeout. Update apiFetch to create and pass an AbortController signal into fetch, add a reasonable timeout that aborts the request, and ensure the timeout is cleared once the request settles. Keep the change localized to apiFetch in api.ts so callers automatically benefit, and make sure the thrown error clearly indicates the request timed out.monai/app/services/tinyfish_client.py (2)
39-47: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winNew
httpx.AsyncClientcreated per request.Both
search_tinyfishandfetch_tinyfishopen a freshAsyncClient(and TCP/TLS handshake) on every call instead of reusing a pooled client. Given these are core, frequently-invoked calls (menu-gap, suppliers, trends routes all call into this module), this adds avoidable latency under load.♻️ Suggested fix: module-level shared client
-async def search_tinyfish( +_client: httpx.AsyncClient | None = None + + +def _get_client(timeout: float) -> httpx.AsyncClient: + global _client + if _client is None: + _client = httpx.AsyncClient(timeout=timeout) + return _client + + +async def search_tinyfish( query: str, max_results: int = 10, *, location: str | None = None, language: str | None = None, purpose: str | None = None, ) -> dict: ... - async with httpx.AsyncClient(timeout=30.0) as client: - response = await client.get(TINYFISH_SEARCH_URL, params=params, headers=_headers()) - response.raise_for_status() - data = response.json() + client = _get_client(30.0) + response = await client.get(TINYFISH_SEARCH_URL, params=params, headers=_headers()) + response.raise_for_status() + data = response.json()Note: a shared client needs an explicit close on app shutdown (e.g., FastAPI lifespan) to avoid leaking connections.
Also applies to: 54-61
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@monai/app/services/tinyfish_client.py` around lines 39 - 47, `search_tinyfish` and `fetch_tinyfish` are creating a new `httpx.AsyncClient` for every call, which bypasses connection pooling and adds latency. Refactor `tinyfish_client.py` to reuse a module-level shared `AsyncClient` (or an app-scoped client) in both functions, keeping the existing request logic intact, and make sure the shared client is explicitly closed during app shutdown via the FastAPI lifespan/startup-shutdown lifecycle.
22-61: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winNo retry/backoff for transient TinyFish API failures.
Both functions propagate any
httpxerror (timeout, connection reset, 5xx) directly viaraise_for_status(); a single transient blip fails the whole route (caught generically upstream as a 500) with no retry. Since these are idempotent read operations, adding limited retries with backoff (e.g., viatenacityor manual retry loop) would improve resilience against TinyFish API flakiness.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@monai/app/services/tinyfish_client.py` around lines 22 - 61, Both search_tinyfish and fetch_tinyfish currently fail immediately on transient httpx errors, so add limited retry-with-backoff around the TinyFish requests in these two functions. Keep the calls idempotent but wrap the AsyncClient get/post plus response.raise_for_status handling in a small retry loop or tenacity policy, retrying on timeouts, connection errors, and 5xx responses before giving up. Use the existing search_tinyfish and fetch_tinyfish entry points so the behavior is localized and easy to locate.monai/app/routes/suppliers.py (2)
29-71: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicated "AI-or-fallback" pattern across both handlers.
discover_suppliersandgenerate_outreachrepeat the sameif has_openai(): prompt → analyze_data → parse_llm_json → fallback-on-type-mismatchshape, andSYSTEM_JSON/similar prompts also appear in other route files per the cross-file context (e.g.,analysis.py). Extracting a shared helper (e.g.,async def ai_or_fallback(prompt, system_prompt, expected_type, fallback_fn)) in a services module would reduce duplication acrosssuppliers.py,analysis.py, andtrends.py.Also applies to: 74-101
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@monai/app/routes/suppliers.py` around lines 29 - 71, The AI-or-fallback flow in discover_suppliers (and the matching handler in the same module) is duplicated and should be centralized. Extract the repeated has_openai() → analyze_data() → parse_llm_json() → type-check → fallback logic into a shared helper in a services module, and have discover_suppliers call it with its prompt, SYSTEM_JSON, and suppliers_from_search fallback so the same pattern can be reused by generate_outreach and other routes like analysis.py/trends.py.
70-71: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winRaw exception details returned to API clients.
detail=str(e)surfaces internal exception text (e.g., third-party API error bodies, or the"TINYFISH_API_KEY environment variable not set"config error) directly in the HTTP response. Prefer logging the full exception server-side and returning a generic message to the client.🛡️ Suggested fix
except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) from e + logger.exception("discover_suppliers failed") + raise HTTPException(status_code=500, detail="Failed to discover suppliers") from eAlso applies to: 100-101
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@monai/app/routes/suppliers.py` around lines 70 - 71, The exception handler in the suppliers route is returning raw internal exception text via HTTPException(detail=str(e)), which can leak sensitive implementation details to API clients. Update the except Exception as e block to log the full exception server-side and raise HTTPException with a generic 500 message instead of using the exception string; apply the same fix anywhere else in this route logic that uses the same pattern.monai/app/services/search_analysis.py (1)
136-147: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRemove unused
menu_tokens.
menu_tokensis computed but never referenced afterward (onlymenu_wordsis used for the overlap comparison).♻️ Proposed fix
trend_signals = _sources_from_results(trends_data, 8) - menu_tokens = {item.lower().strip() for item in current_menu} menu_words = set()🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@monai/app/services/search_analysis.py` around lines 136 - 147, The menu overlap logic in menu_gap_from_search computes menu_tokens but never uses it, so remove that unused variable and keep the tokenization flow focused on menu_words and the later overlap comparison. Make the cleanup in the menu_gap_from_search function without changing the existing behavior of trend_signals, current_menu processing, or competitor_menu analysis.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@monai/app/routes/analysis.py`:
- Around line 90-91: The exception handler in the analysis route is leaking
internal error text by passing str(e) directly into HTTPException(detail=...).
Update the except Exception as e block in the analysis endpoint to log the
caught exception server-side and return a generic 500 response message instead
of the raw exception string. Keep the fix localized to the analysis.py route
handler so the client only receives a safe, non-sensitive error detail.
In `@monai/app/routes/trends.py`:
- Around line 52-53: The three endpoint exception handlers in trends.py are
returning raw Exception text in HTTPException(detail=str(e)), which exposes
internal/upstream error details to clients. Update each of those route handlers
to log the caught exception server-side and return a generic 500 message instead
of str(e), matching the safer pattern used elsewhere (including analysis.py).
Keep the raise from e behavior if needed, but ensure the response detail is
sanitized in all three handlers.
- Around line 74-78: In trends.py, the parse_llm_json fallback in the main
forecast flow should not replace a failed LLM JSON response with only raw
reasoning, because that drops the structured forecast fields. Update the branch
around parse_llm_json so that when ai_response is not a dict, it falls back to
the same search-derived builder used in the non-LLM path (like
forecast_from_search or the equivalent helper pattern used by
get_emerging_trends, compare_regional_trends, and analyze_menu_gap), preserving
confidence_score, projected_mainstream_days, and key_drivers in the returned
payload.
In `@monai/app/services/ai_client.py`:
- Around line 15-22: The _get_client() helper currently creates AsyncOpenAI with
the SDK default timeout, which can leave request handlers waiting too long.
Update the AsyncOpenAI initialization in _get_client() to set an explicit
shorter timeout, or ensure analyze_data() passes a per-request timeout when
using the shared client. Keep the change localized to the client creation path
so all downstream calls inherit the safer default.
In `@monai/app/services/report_builder.py`:
- Around line 108-115: The section built in report_builder’s report assembly can
still call _paragraphs(...)[0] when top has no why_it_matters or description,
which can raise IndexError. Update the logic in the section construction to
guard the first-paragraph access in the report_builder path, using a safe
fallback or omitting the section when no paragraph text exists. Keep the fix
localized around the top_name / top section assembly so the /api/trends/emerging
flow no longer fails on empty LLM content.
In `@monai/frontend/src/components/monai/AnalysisPanel.tsx`:
- Around line 92-118: Stale analysis results can overwrite the newly selected
tab because `runAnalysis` applies `setResult(data)` even after `handleTabChange`
switches tabs. Add a request guard in `AnalysisPanel` (for example, a token/id
or abort-style check) so only the latest in-flight `runAnalysis` call can update
state, and ignore/cancel earlier responses when `activeTab` changes. If you
prefer the simpler approach, also disable the `TabsList`/`TabsTrigger`s while
`loadingTab` is set so users cannot switch tabs during an active request.
In `@monai/frontend/src/hooks/use-mobile.ts`:
- Around line 5-19: The initial mobile state is being deferred until useEffect
runs, causing a brief desktop render on mobile devices. Update useIsMobile to
compute the initial value eagerly from window.matchMedia or window.innerWidth
during the first render, then keep the existing change listener in the effect to
update it on resize. Preserve the useIsMobile hook and its return value so
Sidebar gets the correct mobile state immediately.
In `@monai/main.py`:
- Around line 37-44: The CORS setup in app.add_middleware uses wildcard
allow_headers and expose_headers while allow_credentials=True, which conflicts
with FastAPI guidance. Update the CORSMiddleware configuration to use explicit
header lists instead of ["*"], keeping the existing allow_origins allowlist and
preserving credentials support; use the app.add_middleware call as the reference
point for tightening these settings.
In `@monai/requirements.txt`:
- Line 1: Update the FastAPI dependency in requirements so it is no longer
pinned to 0.111.0 and instead uses 0.115.0 or newer, since that version range
allows a safe Starlette release. Adjust the version specifier in
requirements.txt accordingly and keep the dependency pinned to a compatible
upgraded FastAPI release.
In `@monai/scripts/smoke_test.py`:
- Around line 85-93: The smoke test can crash when a non-JSON error body is
returned because the `check()` message arguments in `get_health`/`GET
/api/trends/emerging` are evaluated eagerly and call `.get()` on the response
body even when `get()` returns a string. Update the `check(...)` call sites in
`smoke_test.py` so any access to `health` or `emerging` fields is guarded by a
type check or deferred until after confirming the body is a dict, using the
existing `get()` helper and `check()` calls to keep failures reported as FAIL
instead of raising `AttributeError`.
---
Nitpick comments:
In `@monai/app/routes/suppliers.py`:
- Around line 29-71: The AI-or-fallback flow in discover_suppliers (and the
matching handler in the same module) is duplicated and should be centralized.
Extract the repeated has_openai() → analyze_data() → parse_llm_json() →
type-check → fallback logic into a shared helper in a services module, and have
discover_suppliers call it with its prompt, SYSTEM_JSON, and
suppliers_from_search fallback so the same pattern can be reused by
generate_outreach and other routes like analysis.py/trends.py.
- Around line 70-71: The exception handler in the suppliers route is returning
raw internal exception text via HTTPException(detail=str(e)), which can leak
sensitive implementation details to API clients. Update the except Exception as
e block to log the full exception server-side and raise HTTPException with a
generic 500 message instead of using the exception string; apply the same fix
anywhere else in this route logic that uses the same pattern.
In `@monai/app/services/search_analysis.py`:
- Around line 136-147: The menu overlap logic in menu_gap_from_search computes
menu_tokens but never uses it, so remove that unused variable and keep the
tokenization flow focused on menu_words and the later overlap comparison. Make
the cleanup in the menu_gap_from_search function without changing the existing
behavior of trend_signals, current_menu processing, or competitor_menu analysis.
In `@monai/app/services/tinyfish_client.py`:
- Around line 39-47: `search_tinyfish` and `fetch_tinyfish` are creating a new
`httpx.AsyncClient` for every call, which bypasses connection pooling and adds
latency. Refactor `tinyfish_client.py` to reuse a module-level shared
`AsyncClient` (or an app-scoped client) in both functions, keeping the existing
request logic intact, and make sure the shared client is explicitly closed
during app shutdown via the FastAPI lifespan/startup-shutdown lifecycle.
- Around line 22-61: Both search_tinyfish and fetch_tinyfish currently fail
immediately on transient httpx errors, so add limited retry-with-backoff around
the TinyFish requests in these two functions. Keep the calls idempotent but wrap
the AsyncClient get/post plus response.raise_for_status handling in a small
retry loop or tenacity policy, retrying on timeouts, connection errors, and 5xx
responses before giving up. Use the existing search_tinyfish and fetch_tinyfish
entry points so the behavior is localized and easy to locate.
In `@monai/frontend/src/components/monai/analysisExamples.ts`:
- Around line 284-291: The report unwrapping logic is duplicated between
extractReport and buildClientReport, so consolidate it into a single helper and
have the other function delegate to it. Update buildClientReport to call
extractReport, or move the shared record.report extraction into
buildClientReport and reuse it from extractReport, keeping the
IntelligenceReport handling in one place and preserving the same null behavior.
In `@monai/frontend/src/components/monai/ProblemTicker.tsx`:
- Around line 3-28: The ticker in ProblemTicker duplicates its items for
animation, which causes screen readers to announce the same content twice.
Update the ProblemTicker component so the repeated ticker track is treated as
decorative by assistive tech, using the existing section/div/span structure and
the ticker-track/items rendering as the place to apply the fix. Ensure only one
accessible announcement remains by hiding the duplicated scrolling content from
screen readers while keeping the visual animation unchanged.
In `@monai/frontend/src/components/monai/VietnamMap.tsx`:
- Around line 3-9: The SVG in VietnamMap has conflicting accessibility
attributes: aria-hidden="true" prevents the element from being exposed to
assistive tech, so its role="img" and <title> are ineffective. Update the svg
markup in VietnamMap to choose one path: if the map is decorative, remove role
and <title>; if it should be announced, remove aria-hidden and keep the
descriptive title/role.
In `@monai/frontend/src/components/ui/progress.tsx`:
- Around line 15-18: Clamp the progress value used in
ProgressPrimitive.Indicator so the translateX calculation stays within 0–100; in
the progress.tsx component, update the style logic that currently uses value ||
0 to normalize out-of-range inputs before computing the transform. Keep the
change localized to the Progress component so callers can still pass raw values
while the bar rendering remains stable for negative or >100 percentages.
In `@monai/frontend/src/components/ui/radio-group.tsx`:
- Around line 7-34: No code change is needed in RadioGroup or RadioGroupItem:
the current React.forwardRef pattern in the radio-group component is acceptable
and should be left as generated boilerplate. Keep the existing
RadioGroupPrimitive.Root and RadioGroupPrimitive.Item wrappers unchanged unless
there is a separate functional issue to address.
In `@monai/frontend/src/components/ui/toggle-group.tsx`:
- Around line 8-11: The ToggleGroupContext default values are forcing every
ToggleGroupItem to use the context’s size and variant, so item-level overrides
never win. Update the ToggleGroupContext and the ToggleGroupItem prop resolution
so an item can supply its own variant/size when present, instead of always
falling back to the default context values. Focus on the ToggleGroupContext
definition and the item rendering logic that reads context.variant and
context.size.
In `@monai/frontend/src/lib/api.ts`:
- Around line 12-46: The apiFetch helper currently waits forever on slow or
hanging requests because it does not enforce a timeout. Update apiFetch to
create and pass an AbortController signal into fetch, add a reasonable timeout
that aborts the request, and ensure the timeout is cleared once the request
settles. Keep the change localized to apiFetch in api.ts so callers
automatically benefit, and make sure the thrown error clearly indicates the
request timed out.
In `@monai/frontend/tsconfig.json`:
- Around line 2-22: Enable TypeScript strict mode in the frontend tsconfig by
adding the strict compiler option inside compilerOptions. Update the
monai/frontend/tsconfig.json configuration so the existing TypeScript setup used
by the Vite frontend is running with strict type checking, and keep the change
localized to the tsconfig compilerOptions block.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: eae4ef20-48cb-437a-982a-2fa2f9ab6e76
⛔ Files ignored due to path filters (13)
monai/frontend/package-lock.jsonis excluded by!**/package-lock.jsonmonai/frontend/public/favicon.svgis excluded by!**/*.svgmonai/frontend/public/icons.svgis excluded by!**/*.svgmonai/frontend/src/assets/banhmi.pngis excluded by!**/*.pngmonai/frontend/src/assets/banhxeo.pngis excluded by!**/*.pngmonai/frontend/src/assets/bunbohue.pngis excluded by!**/*.pngmonai/frontend/src/assets/capthrung.pngis excluded by!**/*.pngmonai/frontend/src/assets/hero.pngis excluded by!**/*.pngmonai/frontend/src/assets/leaf.jpgis excluded by!**/*.jpgmonai/frontend/src/assets/leaf.svgis excluded by!**/*.svgmonai/frontend/src/assets/pho.pngis excluded by!**/*.pngmonai/frontend/src/assets/typescript.svgis excluded by!**/*.svgmonai/frontend/src/assets/vite.svgis excluded by!**/*.svg
📒 Files selected for processing (92)
README.mdmonai/.env.examplemonai/.env.templatemonai/.gitignoremonai/.python-versionmonai/README.mdmonai/app/__init__.pymonai/app/routes/__init__.pymonai/app/routes/analysis.pymonai/app/routes/suppliers.pymonai/app/routes/trends.pymonai/app/services/__init__.pymonai/app/services/ai_client.pymonai/app/services/report_builder.pymonai/app/services/response_parser.pymonai/app/services/search_analysis.pymonai/app/services/tinyfish_client.pymonai/frontend/.env.examplemonai/frontend/.gitignoremonai/frontend/index.htmlmonai/frontend/package.jsonmonai/frontend/src/components/monai/AnalysisPanel.tsxmonai/frontend/src/components/monai/CtaFooter.tsxmonai/frontend/src/components/monai/FeaturesSection.tsxmonai/frontend/src/components/monai/HeroCollage.tsxmonai/frontend/src/components/monai/HeroSection.tsxmonai/frontend/src/components/monai/IntelligenceReport.tsxmonai/frontend/src/components/monai/ProblemTicker.tsxmonai/frontend/src/components/monai/SiteFooter.tsxmonai/frontend/src/components/monai/SiteHeader.tsxmonai/frontend/src/components/monai/StarBullet.tsxmonai/frontend/src/components/monai/TrendsBoard.tsxmonai/frontend/src/components/monai/UsersSection.tsxmonai/frontend/src/components/monai/VietnamMap.tsxmonai/frontend/src/components/monai/WorkflowSection.tsxmonai/frontend/src/components/monai/analysisExamples.tsmonai/frontend/src/components/monai/data.tsmonai/frontend/src/components/ui/accordion.tsxmonai/frontend/src/components/ui/button.tsxmonai/frontend/src/components/ui/checkbox.tsxmonai/frontend/src/components/ui/collapsible.tsxmonai/frontend/src/components/ui/command.tsxmonai/frontend/src/components/ui/context-menu.tsxmonai/frontend/src/components/ui/dialog.tsxmonai/frontend/src/components/ui/drawer.tsxmonai/frontend/src/components/ui/dropdown-menu.tsxmonai/frontend/src/components/ui/form.tsxmonai/frontend/src/components/ui/hover-card.tsxmonai/frontend/src/components/ui/input-otp.tsxmonai/frontend/src/components/ui/input.tsxmonai/frontend/src/components/ui/label.tsxmonai/frontend/src/components/ui/menubar.tsxmonai/frontend/src/components/ui/navigation-menu.tsxmonai/frontend/src/components/ui/pagination.tsxmonai/frontend/src/components/ui/popover.tsxmonai/frontend/src/components/ui/progress.tsxmonai/frontend/src/components/ui/radio-group.tsxmonai/frontend/src/components/ui/resizable.tsxmonai/frontend/src/components/ui/scroll-area.tsxmonai/frontend/src/components/ui/select.tsxmonai/frontend/src/components/ui/separator.tsxmonai/frontend/src/components/ui/sheet.tsxmonai/frontend/src/components/ui/sidebar.tsxmonai/frontend/src/components/ui/skeleton.tsxmonai/frontend/src/components/ui/switch.tsxmonai/frontend/src/components/ui/table.tsxmonai/frontend/src/components/ui/tabs.tsxmonai/frontend/src/components/ui/textarea.tsxmonai/frontend/src/components/ui/toggle-group.tsxmonai/frontend/src/components/ui/toggle.tsxmonai/frontend/src/components/ui/tooltip.tsxmonai/frontend/src/hooks/use-mobile.tsmonai/frontend/src/lib/api.tsmonai/frontend/src/lib/buildClientReport.tsmonai/frontend/src/lib/normalizeResult.tsmonai/frontend/src/lib/utils.tsmonai/frontend/src/main.tsxmonai/frontend/src/routeTree.gen.tsmonai/frontend/src/routes/__root.tsxmonai/frontend/src/routes/dashboard.tsxmonai/frontend/src/routes/index.tsxmonai/frontend/src/styles.cssmonai/frontend/src/vite-env.d.tsmonai/frontend/tsconfig.app.jsonmonai/frontend/tsconfig.jsonmonai/frontend/vercel.jsonmonai/frontend/vite.config.tsmonai/main.pymonai/render.yamlmonai/requirements.txtmonai/runtime.txtmonai/scripts/smoke_test.py
Harden API error handling, forecast fallback, report builder guards, stale tab request handling, CORS headers, FastAPI upgrade, and smoke test robustness.
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
monai/app/routes/suppliers.py (1)
73-99: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winAdd the
/outreachroute decoratorgenerate_outreachis not registered with FastAPI, so/api/suppliers/outreach404s even though the frontend already calls it. Add@router.post("/outreach")above the handler.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@monai/app/routes/suppliers.py` around lines 73 - 99, The generate_outreach handler is defined but never registered, so the supplier outreach endpoint is not exposed through FastAPI. Add the missing router decorator above generate_outreach using the existing router in suppliers.py, and keep the handler name and path consistent so the frontend call to the outreach endpoint resolves correctly.
🧹 Nitpick comments (1)
monai/app/routes/trends.py (1)
98-107: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winSequential region fetches double latency.
results_aandresults_bare awaited one after another instead of concurrently, doubling round-trip time for the regional comparison endpoint.♻️ Proposed fix
- results_a = await search_tinyfish( - f"{category} trends in {region_a} Vietnam", - max_results=5, - purpose=f"Find {category} trends in {region_a}", - ) - results_b = await search_tinyfish( - f"{category} trends in {region_b} Vietnam", - max_results=5, - purpose=f"Find {category} trends in {region_b}", - ) + results_a, results_b = await asyncio.gather( + search_tinyfish( + f"{category} trends in {region_a} Vietnam", + max_results=5, + purpose=f"Find {category} trends in {region_a}", + ), + search_tinyfish( + f"{category} trends in {region_b} Vietnam", + max_results=5, + purpose=f"Find {category} trends in {region_b}", + ), + )(requires
import asyncioat the top of the file)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@monai/app/routes/trends.py` around lines 98 - 107, The regional trend fetches in the trends route are awaited sequentially, which unnecessarily doubles latency for the comparison endpoint. Update the logic around search_tinyfish in the trends handler to run both lookups concurrently using asyncio.gather, and add the needed asyncio import at the top of the file so results_a and results_b are retrieved in parallel.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@monai/app/routes/trends.py`:
- Around line 54-55: The two handlers in trends.py are defined but never exposed
because `forecast_trend` and `compare_regional_trends` are missing router
decorators. Add the appropriate `@router.get` or `@router.post` annotations
directly above those functions so they are registered on `trends.router` and
become available through the mounted router in main.py.
In `@monai/requirements.txt`:
- Line 1: Update the FastAPI dependency in requirements so it no longer keeps
Starlette below 0.42.0, since the current fastapi==0.115.6 pin blocks the
host-header fix. Adjust the version in requirements.txt to a FastAPI release
that permits the newer Starlette line, or add an explicit Starlette override if
that dependency constraint is intentional. Locate the change by the fastapi
dependency entry in requirements.txt.
---
Outside diff comments:
In `@monai/app/routes/suppliers.py`:
- Around line 73-99: The generate_outreach handler is defined but never
registered, so the supplier outreach endpoint is not exposed through FastAPI.
Add the missing router decorator above generate_outreach using the existing
router in suppliers.py, and keep the handler name and path consistent so the
frontend call to the outreach endpoint resolves correctly.
---
Nitpick comments:
In `@monai/app/routes/trends.py`:
- Around line 98-107: The regional trend fetches in the trends route are awaited
sequentially, which unnecessarily doubles latency for the comparison endpoint.
Update the logic around search_tinyfish in the trends handler to run both
lookups concurrently using asyncio.gather, and add the needed asyncio import at
the top of the file so results_a and results_b are retrieved in parallel.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: b0773d8b-e81d-4d8c-b044-777d15b92b79
📒 Files selected for processing (11)
monai/app/http_errors.pymonai/app/routes/analysis.pymonai/app/routes/suppliers.pymonai/app/routes/trends.pymonai/app/services/ai_client.pymonai/app/services/report_builder.pymonai/frontend/src/components/monai/AnalysisPanel.tsxmonai/frontend/src/hooks/use-mobile.tsmonai/main.pymonai/requirements.txtmonai/scripts/smoke_test.py
🚧 Files skipped from review as they are similar to previous changes (7)
- monai/frontend/src/hooks/use-mobile.ts
- monai/app/services/ai_client.py
- monai/main.py
- monai/scripts/smoke_test.py
- monai/app/routes/analysis.py
- monai/frontend/src/components/monai/AnalysisPanel.tsx
- monai/app/services/report_builder.py
| async def forecast_trend(trend_name: str, location: str): | ||
| """Predicts mainstream adoption timeline for an emerging trend.""" |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
rg -n -B3 'async def (forecast_trend|compare_regional_trends)' monai/app/routes/trends.py
rg -n '`@router`\.' monai/app/routes/trends.pyRepository: tinyfish-io/tinyfish-cookbook
Length of output: 555
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- trends.py (1-130) ---'
sed -n '1,130p' monai/app/routes/trends.py
echo
echo '--- router mounts ---'
rg -n 'include_router|APIRouter|trends\.router|routes\.trends' monai/app -g '*.py'Repository: tinyfish-io/tinyfish-cookbook
Length of output: 6862
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- main / app entrypoints ---'
rg -n 'include_router|APIRouter|trends\.router|from app\.routes import trends|import .*trends' monai -g '*.py'
echo
echo '--- end of trends.py ---'
tail -n +120 monai/app/routes/trends.py | cat -nRepository: tinyfish-io/tinyfish-cookbook
Length of output: 2395
Add route decorators to forecast_trend and compare_regional_trends. main.py already mounts trends.router, but these two handlers have no @router.* decorator, so they aren’t registered as endpoints. monai/app/routes/trends.py:54,95
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@monai/app/routes/trends.py` around lines 54 - 55, The two handlers in
trends.py are defined but never exposed because `forecast_trend` and
`compare_regional_trends` are missing router decorators. Add the appropriate
`@router.get` or `@router.post` annotations directly above those functions so
they are registered on `trends.router` and become available through the mounted
router in main.py.
| @@ -0,0 +1,6 @@ | |||
| fastapi==0.115.6 | |||
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
git ls-files monai/requirements.txt pyproject.toml setup.py setup.cfg requirements*.txtRepository: tinyfish-io/tinyfish-cookbook
Length of output: 190
🏁 Script executed:
cat -n monai/requirements.txtRepository: tinyfish-io/tinyfish-cookbook
Length of output: 319
🏁 Script executed:
python3 - <<'PY'
import pathlib, re, sys
for p in pathlib.Path('.').rglob('*'):
if p.is_file() and p.name in {'pyproject.toml','setup.py','setup.cfg','requirements.txt'}:
text = p.read_text(errors='ignore')
if 'fastapi' in text.lower() or 'starlette' in text.lower():
print(f'== {p} ==')
for i, line in enumerate(text.splitlines(), 1):
if re.search(r'fastapi|starlette', line, re.I):
print(f'{i}: {line}')
PYRepository: tinyfish-io/tinyfish-cookbook
Length of output: 216
🌐 Web query:
FastAPI 0.115.6 starlette dependency constraint release notes host-header validation fix Starlette version
💡 Result:
FastAPI version 0.115.6 does not include a fix for host-header validation because the host-header validation issue (tracked as CVE-2026-48710) was identified later and addressed in Starlette 1.0.1 [1][2]. FastAPI 0.115.6 enforces a dependency on Starlette within the range >=0.40.0, <0.42.0 [3][4]. Because this version range is capped below 1.0.1, FastAPI 0.115.6 is inherently vulnerable to the host-header validation flaw (often referred to as the BadHost vulnerability) if the application relies on request.url.path for security-sensitive decisions [1][5][6]. Key details regarding this vulnerability and dependency state: 1. Vulnerability: CVE-2026-48710 (GHSA-86qp-5c8j-p5mr) affects all Starlette versions prior to 1.0.1 [1][2]. It allows attackers to bypass path-based authentication by injecting crafted characters (such as /,?, or #) into the HTTP Host header, which causes Starlette to reconstruct an incorrect request.url.path [1][6][7]. 2. Dependency Constraint: FastAPI 0.115.6 explicitly limits Starlette to <0.42.0 [3][4]. To mitigate this vulnerability, users must ensure they are using a version of Starlette >=1.0.1 [1][6]. 3. Recommended Mitigation: If you are using an older version of FastAPI that cannot be upgraded to a version compatible with Starlette 1.0.1+, you should deploy an RFC-compliant reverse proxy (e.g., Nginx, Caddy, or Traefik) that validates and sanitizes the Host header before forwarding requests to the ASGI server [5][6][7]. Additionally, in your application code, replace references to request.url.path with request.scope["path"], which provides the raw, un-reconstructed path [5][7].
Citations:
- 1: Does Starlette GHSA-86qp-5c8j-p5mr affect FastAPI installations using Starlette <= 1.0.0? fastapi/fastapi#15593
- 2: https://www.sentinelone.com/vulnerability-database/cve-2026-48710/
- 3: https://pypi.org/project/fastapi/0.115.6/
- 4: CVE-2025-54121 update Starlette>=0.47.2 Chainlit/chainlit#2333
- 5: https://x41-dsec.de/lab/advisories/x41-2026-002-starlette/
- 6: https://badhost.org/
- 7: https://ostif.org/disclosing-the-badhost-vulnerability-in-starlette/
Upgrade FastAPI past the Starlette cap. fastapi==0.115.6 still pins Starlette below 0.42.0, so it can’t pick up the 1.0.1+ host-header fix. Bump FastAPI to a release that allows that Starlette line, or add an explicit Starlette override if that’s intentional.
🧰 Tools
🪛 OSV Scanner (2.4.0)
[HIGH] 1-1: starlette 0.41.3: BadHost: Missing Host header validation poisons request.url.path, bypassing path-based security checks
(PYSEC-2026-161)
[HIGH] 1-1: starlette 0.41.3: undefined
(PYSEC-2026-248)
[HIGH] 1-1: starlette 0.41.3: undefined
(PYSEC-2026-249)
[HIGH] 1-1: starlette 0.41.3: Starlette has possible denial-of-service vector when parsing large files in multipart forms
[HIGH] 1-1: starlette 0.41.3: Starlette vulnerable to O(n^2) DoS via Range header merging in starlette.responses.FileResponse
[HIGH] 1-1: starlette 0.41.3: Starlette: request.form() limits silently ignored for application/x-www-form-urlencoded enable DoS
[HIGH] 1-1: starlette 0.41.3: Starlette has missing Host header validation that poisons request.url.path, bypassing path-based security checks
[HIGH] 1-1: starlette 0.41.3: Starlette: Unvalidated request path concatenated into authority poisons request.url.hostname
[HIGH] 1-1: starlette 0.41.3: Starlette: SSRF and NTLM credential theft via UNC paths in StaticFiles on Windows
[HIGH] 1-1: starlette 0.41.3: Starlette: Arbitrary HTTP method dispatched to HTTPEndpoint attributes via getattr
[HIGH] 1-1: tqdm 4.9.0: undefined
(PYSEC-2017-74)
[HIGH] 1-1: tqdm 4.9.0: tqdm CLI arguments injection attack
[HIGH] 1-1: tqdm 4.9.0: TDQM Arbitrary Code Execution
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@monai/requirements.txt` at line 1, Update the FastAPI dependency in
requirements so it no longer keeps Starlette below 0.42.0, since the current
fastapi==0.115.6 pin blocks the host-header fix. Adjust the version in
requirements.txt to a FastAPI release that permits the newer Starlette line, or
add an explicit Starlette override if that dependency constraint is intentional.
Locate the change by the fastapi dependency entry in requirements.txt.
Source: Linters/SAST tools
Re-add @router decorators dropped during error-handler refactor, run regional TinyFish searches in parallel, and pin starlette>=0.42.
Summary
Summary by CodeRabbit