feat: HLS streaming, transcripts, agent course generation & landing-page course previews (#663)
New features: - HLS video encoding/streaming (hls routes, hls-encoder) and a media transcripts pipeline (transcripts route + media-transcript queries) - AI assistant chat vs. run model: agent-course-generation worker/queue, run-monitor pane, chat-changes card, content ask-ai bar - Per-theme course-page previews across org landing-page themes (bold, classic, corporate, editorial, etc.) with a shared course token/component set - View-as-student mode and login-link / link-invite invite flows Refactor: - Extract packages/core consolidating config (env, storage, dashboard-url), services and utils previously duplicated under apps/api/src/config - Remove legacy agent service modules (chat-tools, chat-context, context-window, document, fetch-url, lesson-content) - Relax Cross-Origin-Resource-Policy to allow cross-origin media embedding - Add format script to tenant-router so turbo run format covers it
This commit is contained in:
@@ -15,19 +15,20 @@ export const getRequestBaseUrl = () => {
|
||||
if (typeof window === 'undefined') {
|
||||
// When on the server, we want to hit the private url which is the docker container of `api` or the private network url of `api`.
|
||||
// if that isn't set then it will fallback to the public url of the `api`
|
||||
// this doesn't need proxy because on the server we are using our api key to make queries.
|
||||
return process.env.PRIVATE_SERVER_URL || env.PUBLIC_SERVER_URL;
|
||||
}
|
||||
|
||||
// Self-hosted: dashboard and API are on different subdomains of the
|
||||
// operator's apex. Browser calls go straight to PUBLIC_SERVER_URL;
|
||||
// cookies cross subdomains via AUTH_COOKIE_DOMAIN. No Worker proxy.
|
||||
if (env.PUBLIC_IS_SELFHOSTED === 'true' || dev) {
|
||||
// Local dev keeps the direct API path so existing Vite/API workflows do not
|
||||
// depend on the SvelteKit proxy.
|
||||
if (dev) {
|
||||
return env.PUBLIC_SERVER_URL ?? '';
|
||||
}
|
||||
|
||||
// Cloud (multi-tenant): same-origin via the Cloudflare Worker `/proxy`
|
||||
// prefix so auth cookies stay host-only on whichever tenant or BYOD
|
||||
// domain the user is currently visiting. The Worker strips `/proxy` before forwarding to the API
|
||||
// Browser calls are same-origin in production. For self-hosted Node builds,
|
||||
// hooks.server.ts proxies `/proxy/*` to PRIVATE_SERVER_URL.
|
||||
// For cloud multi-tenant builds, the Cloudflare Worker handles the same
|
||||
// prefix. In both cases auth cookies stay first-party on the dashboard host.
|
||||
return `${window.location.origin}/proxy`;
|
||||
};
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@ import { dev } from '$app/environment';
|
||||
|
||||
// Browser baseURL depends on deployment shape:
|
||||
//
|
||||
// - Self-hosted: dashboard and API are on different subdomains under
|
||||
// one apex. Browser calls go straight to PUBLIC_SERVER_URL; cookies
|
||||
// cross subdomains via AUTH_COOKIE_DOMAIN. No Worker proxy.
|
||||
// - Self-hosted: browser auth calls stay on the dashboard origin at
|
||||
// `/api/auth/*`; hooks.server.ts forwards them to the API over
|
||||
// PRIVATE_SERVER_URL.
|
||||
//
|
||||
// - Cloud (multi-tenant): same-origin via the Cloudflare Worker so
|
||||
// auth cookies stay host-only on whichever tenant/BYOD domain the
|
||||
@@ -23,9 +23,12 @@ function resolveBaseURL() {
|
||||
if (typeof window === 'undefined') {
|
||||
return env.PUBLIC_SERVER_URL || 'http://localhost:3002';
|
||||
}
|
||||
if (env.PUBLIC_IS_SELFHOSTED === 'true' || dev) {
|
||||
if (dev) {
|
||||
return env.PUBLIC_SERVER_URL || `${window.location.origin}/api/auth`;
|
||||
}
|
||||
if (env.PUBLIC_IS_SELFHOSTED === 'true') {
|
||||
return `${window.location.origin}/api/auth`;
|
||||
}
|
||||
return `${window.location.origin}/proxy/api/auth`;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import { getRequestBaseUrl } from '$lib/utils/services/api';
|
||||
export const getSessionData = async (cookies: Cookies): Promise<App.Locals | null> => {
|
||||
try {
|
||||
const cioCookies = getCioCookieString(cookies);
|
||||
console.log('cioCookies', cioCookies);
|
||||
|
||||
if (!cioCookies) return null;
|
||||
|
||||
@@ -39,6 +40,7 @@ export async function getThroughTrpc(allCookies: string) {
|
||||
export async function getThroughAuthClient(allCookies: string) {
|
||||
const baseURL = getRequestBaseUrl();
|
||||
console.log('baseURL', baseURL);
|
||||
console.log('allCookies', allCookies);
|
||||
const session = await authServerClient.getSession({
|
||||
fetchOptions: {
|
||||
headers: {
|
||||
|
||||
@@ -38,7 +38,10 @@ export const initPosthog = (user?: PosthogBootstrapUser): void => {
|
||||
if (dev || hasNoTracking()) return;
|
||||
|
||||
posthog.init('phc_JfdHOZ6v0cVlGELBYx1Tmoen2nxNOrAzvgvrPA6Ksov', {
|
||||
api_host: 'https://eu.posthog.com',
|
||||
// Route PostHog through our own domain via the tenant-router Worker so the
|
||||
// cookie is first-party and doesn't trigger the Lighthouse "third-party cookie" deduction.
|
||||
api_host: `${window.location.origin}/ingest`,
|
||||
ui_host: 'https://eu.posthog.com',
|
||||
...(user && {
|
||||
bootstrap: { distinctID: user.id, isIdentifiedID: true }
|
||||
})
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { dev } from '$app/environment';
|
||||
import { get } from 'svelte/store';
|
||||
import { globalStore } from '$lib/utils/store/app';
|
||||
import { licenseApi } from '$features/license/api/license.svelte';
|
||||
|
||||
const USERJOT_APP_ID = 'cm4a6vcmp00jpmdb5n66rmkzz';
|
||||
|
||||
@@ -9,7 +8,6 @@ let isInitialized = false;
|
||||
|
||||
function isWidgetAllowed(): boolean {
|
||||
if (dev) return false;
|
||||
if (licenseApi.hasAccess('no-tracking')) return false;
|
||||
if (get(globalStore).isOrgSite) return false;
|
||||
|
||||
return true;
|
||||
@@ -44,7 +42,7 @@ export function initUserJot(): void {
|
||||
ensureSdkLoaded();
|
||||
|
||||
window.uj.init(USERJOT_APP_ID, {
|
||||
widget: true,
|
||||
trigger: 'custom',
|
||||
position: 'right',
|
||||
theme: 'auto'
|
||||
});
|
||||
@@ -75,3 +73,21 @@ export function identifyUserJotUser({ id, email, fullname, avatarUrl }: UserJotI
|
||||
avatar: avatarUrl ?? undefined
|
||||
});
|
||||
}
|
||||
|
||||
export function clearUserJotUser(): void {
|
||||
if (!isWidgetAllowed()) return;
|
||||
|
||||
ensureSdkLoaded();
|
||||
|
||||
window.uj.identify(null);
|
||||
}
|
||||
|
||||
export type UserJotWidgetSection = 'feedback' | 'roadmap' | 'updates';
|
||||
|
||||
export function showUserJotWidget(section: UserJotWidgetSection): void {
|
||||
if (!isWidgetAllowed()) return;
|
||||
|
||||
ensureSdkLoaded();
|
||||
|
||||
window.uj.showWidget({ section });
|
||||
}
|
||||
|
||||
@@ -10,7 +10,10 @@ declare global {
|
||||
theme?: 'auto' | 'light' | 'dark';
|
||||
}
|
||||
) => void;
|
||||
identify: (user: { id: string; email?: string; firstName?: string; lastName?: string; avatar?: string }) => void;
|
||||
identify: (
|
||||
user: { id: string; email?: string; firstName?: string; lastName?: string; avatar?: string } | null
|
||||
) => void;
|
||||
showWidget: (options: { section: 'feedback' | 'roadmap' | 'updates' }) => void;
|
||||
[method: string]: (...args: unknown[]) => void;
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user