-
Notifications
You must be signed in to change notification settings - Fork 1
Feat/base path and root path #252
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| /** | ||
| * Deployment base path helper. | ||
| * | ||
| * When the app is served under a path prefix (NEXT_PUBLIC_BASE_PATH, see | ||
| * `next.config.ts`), Next.js prefixes what it controls on its own: `<Link>` | ||
| * hrefs, `router.push`, and every `_next/*` asset URL. | ||
| * | ||
| * It does NOT touch two things, which is what this helper is for: | ||
| * 1. files served from `public/` and referenced by a literal path | ||
| * (`/logo-mark.svg`) — documented Next.js behaviour; | ||
| * 2. plain `<a href="/…">` anchors to internal routes, which bypass the | ||
| * router entirely. | ||
| * | ||
| * With no prefix configured (the GitHub Pages and dedicated-host deploys) | ||
| * BASE_PATH is "" and every value passes through unchanged. | ||
| */ | ||
| export const BASE_PATH = (process.env.NEXT_PUBLIC_BASE_PATH ?? "").replace(/\/+$/, ""); | ||
|
|
||
| /** Prefix a root-relative URL with the deployment base path. */ | ||
| export function withBasePath(path: string): string { | ||
| if (!BASE_PATH || !path.startsWith("/")) return path; | ||
| return `${BASE_PATH}${path}`; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,11 +2,20 @@ import type { NextConfig } from "next"; | |
|
|
||
| // Static export for GitHub Pages — no server-side runtime in production. | ||
| // The frontend talks directly to the backend at `NEXT_PUBLIC_API_URL`. | ||
| // | ||
| // `basePath` is opt-in via NEXT_PUBLIC_BASE_PATH at BUILD time (a static | ||
| // export inlines every URL, so it cannot be a runtime setting). Unset — the | ||
| // GitHub Pages and dedicated-host deploys — keeps the bundle at the root | ||
| // exactly as before. Set it to e.g. `/opendata` when the app is served under | ||
| // a path prefix on a host shared with other projects. | ||
| const basePath = process.env.NEXT_PUBLIC_BASE_PATH?.replace(/\/+$/, "") || undefined; | ||
|
|
||
| const nextConfig: NextConfig = { | ||
| output: "export", | ||
| images: { unoptimized: true }, | ||
| poweredByHeader: false, | ||
| trailingSlash: true, | ||
| ...(basePath ? { basePath } : {}), | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a prefixed build also has OIDC enabled, authentication remains rooted at the host: Useful? React with 👍 / 👎. |
||
| }; | ||
|
|
||
| export default nextConfig; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,7 +16,17 @@ | |
| from pydantic import AliasChoices, Field | ||
| from pydantic_settings import BaseSettings, SettingsConfigDict | ||
|
|
||
| Provider = Literal["auto", "ollama", "ollama_cloud", "azure_foundry", "claude"] | ||
| Provider = Literal["auto", "llamacpp", "ollama", "ollama_cloud", "azure_foundry", "claude"] | ||
|
|
||
| # Provider serviti da noi, senza costo per chiamata: niente da tariffare e | ||
| # modelli piccoli, quindi report in modalita' sintetica. Ollama Cloud e Claude | ||
| # NON sono qui: sono a consumo. | ||
| SELF_HOSTED_PROVIDERS: frozenset[str] = frozenset({"llamacpp", "ollama"}) | ||
|
|
||
|
|
||
| def is_self_hosted(provider: str) -> bool: | ||
| """True per i motori locali che gestiamo noi (llama.cpp, Ollama).""" | ||
| return provider in SELF_HOSTED_PROVIDERS | ||
|
|
||
|
|
||
| # Verbatim copy of ckan_agent.config.AGENT_INSTRUCTIONS — keep in sync. | ||
|
|
@@ -1280,6 +1290,16 @@ class Settings(BaseSettings): | |
| eurostat_sdmx_base_url: str = Field(default=_EUROSTAT_BASE_URL) | ||
| oecd_sdmx_base_url: str = Field(default=_OECD_BASE_URL) | ||
|
|
||
| # llama.cpp / llama-swap — server di inferenza self-hosted, compatibile | ||
| # OpenAI. E' il motore predefinito quando non ci sono credenziali cloud. | ||
| # Il valore puo' includere o meno /v1: build_chat_client normalizza. | ||
| llamacpp_base_url: str = Field(default="http://localhost:8080") | ||
| # Id logico del modello = chiave del config.yaml di llama-swap. | ||
| llamacpp_model: str = Field(default="qwen3.5-9b") | ||
| # Solo se llama-server gira con --api-key. L'SDK OpenAI pretende comunque | ||
| # una stringa non vuota, quindi build_chat_client ne mette una fittizia. | ||
| llamacpp_api_key: str | None = Field(default=None) | ||
|
|
||
| # Ollama (OpenAI-compatible) | ||
| ollama_base_url: str = Field(default="http://localhost:11434") | ||
| ollama_llm_model: str = Field(default="qwen2.5:16k") | ||
|
|
@@ -1456,6 +1476,13 @@ class Settings(BaseSettings): | |
| # (e.g. https://opendata.<your-domain>). In local dev keep localhost:3000. | ||
| cors_allow_origins: str = Field(default="http://localhost:3000") | ||
|
|
||
| # Path prefix a reverse proxy strips before forwarding (e.g. | ||
| # ``/opendata/api``). Empty when the app owns its hostname. It does not | ||
| # change the routes the app matches — it only makes the OpenAPI schema and | ||
| # the /docs page advertise URLs that are valid from the outside, instead of | ||
| # a root-relative /openapi.json that would land on another service. | ||
| root_path: str = Field(default="") | ||
|
|
||
| # Base URL pubblica del frontend OpenData AI. Usata per i link assoluti negli | ||
| # export embeddabili (es. la scorecard di maturità in Markdown linka la scheda | ||
| # `/maturita` e la guida `/guida-open-data`). In prod è il dominio pubblico. | ||
|
|
@@ -1832,11 +1859,16 @@ def resolve_provider(settings: Settings) -> Provider: | |
| 1. claude — if ANTHROPIC_API_KEY is set | ||
| 2. azure_foundry — if AZURE_AI_PROJECT_ENDPOINT + deployment name are set | ||
| 3. ollama_cloud — if OLLAMA_CLOUD_API_KEY is set (hosted, metered) | ||
| 4. ollama — fallback (local inference; OLLAMA_BASE_URL may point at | ||
| a remote inference container in production) | ||
| 4. llamacpp — fallback (self-hosted llama.cpp/llama-swap inference | ||
| server; LLAMACPP_BASE_URL may point at a remote host) | ||
|
|
||
| Claude wins over Ollama Cloud when both keys are present, and any cloud key | ||
| wins over the local engine — impostare ANTHROPIC_API_KEY *e'* la | ||
| configurazione che seleziona Claude. A user's own BYOK credential overrides | ||
| this entirely (see llm_access / build_chat_client). | ||
|
|
||
| Claude wins over Ollama Cloud when both keys are present. A user's own BYOK | ||
| credential overrides this entirely (see llm_access / build_chat_client). | ||
| NOTA — il fallback era "ollama": chi ci contava implicitamente deve ora | ||
| dichiarare LLM_PROVIDER=ollama. | ||
| """ | ||
| if settings.llm_provider != "auto": | ||
| return settings.llm_provider | ||
|
|
@@ -1846,7 +1878,7 @@ def resolve_provider(settings: Settings) -> Provider: | |
| return "azure_foundry" | ||
| if settings.ollama_cloud_api_key: | ||
| return "ollama_cloud" | ||
| return "ollama" | ||
| return "llamacpp" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
With the documented Useful? React with 👍 / 👎. |
||
|
|
||
|
|
||
| # ── Report depth tiering (territorio) ────────────────────────────────────── | ||
|
|
@@ -1869,14 +1901,14 @@ def resolve_provider(settings: Settings) -> Provider: | |
| def resolve_report_depth(settings: "Settings") -> Literal["full", "concise"]: | ||
| """Profondità del report territorio in base alla capacità del modello. | ||
|
|
||
| `auto` (default): `concise` per ollama locale (modelli piccoli), `full` per | ||
| claude/azure_foundry/ollama_cloud (capaci/cloud). Forzabile con | ||
| `REPORT_DEPTH=full|concise`. | ||
| `auto` (default): `concise` per i motori self-hosted (llama.cpp, Ollama — | ||
| modelli piccoli), `full` per claude/azure_foundry/ollama_cloud | ||
| (capaci/cloud). Forzabile con `REPORT_DEPTH=full|concise`. | ||
| """ | ||
| mode = settings.report_depth | ||
| if mode in ("full", "concise"): | ||
| return mode # type: ignore[return-value] | ||
| return "concise" if resolve_provider(settings) == "ollama" else "full" | ||
| return "concise" if is_self_hosted(resolve_provider(settings)) else "full" | ||
|
|
||
|
|
||
| def get_settings() -> Settings: | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Declaring this build argument is insufficient for the repository's supported build paths: the
opendata-ai-uibuild args indocker-compose.ymland thebuild-argsblock in.github/workflows/docker-publish.ymlforward the otherNEXT_PUBLIC_*values but notNEXT_PUBLIC_BASE_PATH. Consequently, setting the new variable in the compose environment or GitHub repository variables still produces a root-based static bundle, so the advertised prefixed deployment fails unless someone bypasses both build paths and invokes Docker manually.Useful? React with 👍 / 👎.