Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion site/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type ReactNode, useEffect, useState } from "react";
import React, { type ReactNode, useEffect, useState } from "react";

const REPO = "GeneralUserModels/tada";
const RELEASE_URL = `https://github.com/${REPO}/releases/latest`;
Expand Down
2 changes: 1 addition & 1 deletion site/src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { StrictMode } from "react";
import React, { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { App } from "./App";
import "./App.css";
Expand Down
4 changes: 3 additions & 1 deletion src/apps/moments/discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from datetime import datetime
from pathlib import Path

from server.feature_flags import is_enabled

logger = logging.getLogger(__name__)


Expand Down Expand Up @@ -100,7 +102,7 @@ async def run_moments_discovery(state) -> None:
logger.info("Next discovery run at %s (in %.0fs)", next_run, delay)
await asyncio.sleep(delay)

if not state.config.moments_enabled:
if not (is_enabled(state.config, "moments") and state.config.moments_enabled):
continue

cfg = state.config
Expand Down
3 changes: 2 additions & 1 deletion src/apps/moments/scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

from apps.moments.execute import run as execute_moment, _parse_frontmatter as parse_frontmatter
from apps.moments.state import load_state
from server.feature_flags import is_enabled

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -138,7 +139,7 @@ async def run_moments_scheduler(state) -> None:
try:
await asyncio.sleep(SCAN_INTERVAL)

if not state.config.moments_enabled:
if not (is_enabled(state.config, "moments") and state.config.moments_enabled):
continue

cfg = state.config
Expand Down
5 changes: 4 additions & 1 deletion src/client/renderer/App.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import React from "react";
import { useAppContext } from "./context/AppContext";
import { useFeatureFlag } from "./featureFlags";
import { Sidebar } from "./components/Sidebar";
import { ConnectorsView } from "./components/views/ConnectorsView";
import { UserModelView } from "./components/views/UserModelView";
Expand All @@ -8,6 +10,7 @@ import { UpdateBanner } from "./components/UpdateBanner";

export function App() {
const { state, dispatch } = useAppContext();
const momentsEnabled = useFeatureFlag("moments");

const navigate = (view: typeof state.activeView) => {
dispatch({ type: "NAVIGATE", view });
Expand All @@ -30,7 +33,7 @@ export function App() {
/>
)}
{state.activeView === "connectors" && <ConnectorsView />}
{state.activeView === "tada" && <TadaView />}
{state.activeView === "tada" && momentsEnabled && <TadaView />}
{state.activeView === "usermodel" && <UserModelView />}
{state.activeView === "settings" && <SettingsView />}
</main>
Expand Down
16 changes: 15 additions & 1 deletion src/client/renderer/components/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import React from "react";
import { ActiveView } from "../context/AppContext";
import { useFeatureFlags, getFlag } from "../featureFlags";

interface Props {
activeView: ActiveView;
Expand Down Expand Up @@ -48,7 +50,19 @@ const navItems: { view: ActiveView; label: string; icon: JSX.Element }[] = [
},
];

const FLAG_FOR_VIEW: Partial<Record<ActiveView, string>> = {
tada: "moments",
};

export function Sidebar({ activeView, connected, onNavigate }: Props) {
const featureFlags = useFeatureFlags();

const visibleItems = navItems.filter(({ view }) => {
const flag = FLAG_FOR_VIEW[view];
if (flag && !getFlag(featureFlags, flag)) return false;
return true;
});

return (
<nav id="sidebar">
<div className="sidebar-brand">
Expand All @@ -72,7 +86,7 @@ export function Sidebar({ activeView, connected, onNavigate }: Props) {
</div>

<div className="sidebar-nav">
{navItems.map(({ view, label, icon }) => (
{visibleItems.map(({ view, label, icon }) => (
<button
key={view}
className={`nav-item${activeView === view ? " active" : ""}`}
Expand Down
2 changes: 2 additions & 0 deletions src/client/renderer/components/UpdateBanner.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import React from "react";

const DOWNLOAD_URL = "https://github.com/GeneralUserModels/tada-release/releases/latest";

interface Props {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useState } from "react";
import React, { useEffect, useState } from "react";

const CONNECTOR_ICONS: Record<string, JSX.Element> = {
monitor: (
Expand Down
1 change: 1 addition & 0 deletions src/client/renderer/components/dashboard/PipelineTile.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import React from "react";
import { TrainingState } from "../../hooks/useTraining";

const stateLabels: Record<TrainingState, string> = {
Expand Down
2 changes: 2 additions & 0 deletions src/client/renderer/components/dashboard/PredictionCard.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import React from "react";

interface Props {
prediction: { actions?: string; error?: string; timestamp?: string } | null;
}
Expand Down
1 change: 1 addition & 0 deletions src/client/renderer/components/dashboard/RewardsChart.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import React from "react";
import {
LineChart,
Line,
Expand Down
2 changes: 1 addition & 1 deletion src/client/renderer/components/modals/PermissionModal.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useState } from "react";
import React, { useEffect, useState } from "react";
import { updateConnector } from "../../api/client";

interface Props {
Expand Down
36 changes: 32 additions & 4 deletions src/client/renderer/components/onboarding/Onboarding.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { useEffect, useRef, useState } from "react";
import React, { useEffect, useRef, useState } from "react";
import { AdvancedLLMSection } from "../shared/AdvancedLLMSection";
import { ModelDropdown, LLM_MODELS, TINKER_MODELS } from "../shared/ModelDropdown";
import { PermissionModal } from "../modals/PermissionModal";
import { getFlag } from "../../featureFlags";
import {
startGoogleSignIn,
getGoogleUser,
Expand All @@ -10,6 +11,7 @@ import {
checkNotificationsPermission,
checkFilesystemPermission,
checkBrowserCookiesPermission,
getSettings,
updateSettings,
completeOnboarding,
} from "../../api/client";
Expand All @@ -36,6 +38,10 @@ export function Onboarding() {
const [step, setStep] = useState(0);
const [permModal, setPermModal] = useState<{ name: string; onGranted: () => void } | null>(null);

// Feature flags (loaded from server settings)
const [ff, setFf] = useState<Record<string, boolean> | undefined>(undefined);
const flag = (name: string) => getFlag(ff, name);

// Google login state
const [googleUser, setGoogleUser] = useState<{ name: string; email: string } | null>(null);
const [googleLoading, setGoogleLoading] = useState(false);
Expand All @@ -55,8 +61,12 @@ export function Onboarding() {
const [connectingGoogle, setConnectingGoogle] = useState<string | null>(null);
const [connectingOutlook, setConnectingOutlook] = useState(false);

// Restore saved Google user on mount; if already signed in, resume at step 2
// Load feature flags + restore saved Google user on mount
useEffect(() => {
getSettings()
.then((s) => setFf((s as Record<string, unknown>).feature_flags as Record<string, boolean> | undefined))
.catch(() => {});

getGoogleUser().then(user => {
if (user) {
setGoogleUser(user);
Expand Down Expand Up @@ -277,6 +287,7 @@ export function Onboarding() {
<div className="glass-card" style={{ padding: 16 }}>
<div className="connector-list">
{/* Screen Recording */}
{flag("permission_screen") && (
<div className="connector-row">
<div className="connector-icon">
<svg width="14" height="14" viewBox="0 0 16 16" fill="none"><rect x="2" y="3" width="12" height="10" rx="2" stroke="currentColor" strokeWidth="1.3"/><circle cx="8" cy="8" r="2" fill="currentColor"/></svg>
Expand All @@ -292,8 +303,10 @@ export function Onboarding() {
}
</div>
</div>
)}

{/* Google (Calendar + Gmail) */}
{(flag("connector_gmail") || flag("connector_calendar")) && (
<div className="connector-row">
<div className="connector-icon">
<svg width="14" height="14" viewBox="0 0 16 16" fill="none"><rect x="2" y="3" width="12" height="11" rx="1.5" stroke="currentColor" strokeWidth="1.3"/><path d="M2 6.5h12" stroke="currentColor" strokeWidth="1.3"/><path d="M5 1.5v3M11 1.5v3" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round"/></svg>
Expand All @@ -309,8 +322,10 @@ export function Onboarding() {
}
</div>
</div>
)}

{/* Outlook (shared auth for calendar + email) */}
{(flag("connector_outlook_email") || flag("connector_outlook_calendar")) && (
<div className="connector-row">
<div className="connector-icon">
<svg width="14" height="14" viewBox="0 0 16 16" fill="none"><rect x="2" y="3" width="12" height="11" rx="1.5" stroke="currentColor" strokeWidth="1.3"/><path d="M2 6.5h12" stroke="currentColor" strokeWidth="1.3"/><path d="M5 1.5v3M11 1.5v3" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round"/></svg>
Expand All @@ -326,8 +341,10 @@ export function Onboarding() {
}
</div>
</div>
)}

{/* Disk Access (Notifications + Filesystem) */}
{flag("permission_notifications") && (
<div className="connector-row">
<div className="connector-icon">
<svg width="14" height="14" viewBox="0 0 16 16" fill="none"><path d="M2 4.5V13a1 1 0 001 1h10a1 1 0 001-1V6a1 1 0 00-1-1H7.5L6 3H3a1 1 0 00-1 1.5z" stroke="currentColor" strokeWidth="1.3" strokeLinejoin="round"/></svg>
Expand All @@ -343,8 +360,10 @@ export function Onboarding() {
}
</div>
</div>
)}

{/* Browser Cookies */}
{flag("permission_browser_cookies") && (
<div className="connector-row">
<div className="connector-icon">
<svg width="14" height="14" viewBox="0 0 16 16" fill="none"><circle cx="8" cy="8" r="6" stroke="currentColor" strokeWidth="1.3"/><path d="M2 8h12M8 2c-2 2-2 10 0 12M8 2c2 2 2 10 0 12" stroke="currentColor" strokeWidth="1.3"/></svg>
Expand All @@ -360,8 +379,10 @@ export function Onboarding() {
}
</div>
</div>
)}

{/* Accessibility (Tabracadabra) */}
{flag("permission_accessibility") && (
<div className="connector-row">
<div className="connector-icon">
<svg width="14" height="14" viewBox="0 0 16 16" fill="none"><path d="M8 1.5a6.5 6.5 0 100 13 6.5 6.5 0 000-13z" stroke="currentColor" strokeWidth="1.3"/><circle cx="8" cy="5.5" r="1" fill="currentColor"/><path d="M5.5 7.5h5M8 7.5v4M6.5 11.5L8 9.5l1.5 2" stroke="currentColor" strokeWidth="1" strokeLinecap="round" strokeLinejoin="round"/></svg>
Expand All @@ -377,11 +398,16 @@ export function Onboarding() {
}
</div>
</div>
)}
</div>
</div>
<div className="btn-row">
<button className="btn btn-ghost" onClick={() => setStep(1)}>Back</button>
<button className="btn btn-primary" disabled={!screenGranted || !browserCookiesGranted || !accessibilityGranted} onClick={() => setStep(3)}>Continue</button>
<button className="btn btn-primary" disabled={
(flag("permission_screen") && !screenGranted)
|| (flag("permission_browser_cookies") && !browserCookiesGranted)
|| (flag("permission_accessibility") && !accessibilityGranted)
} onClick={() => setStep(3)}>Continue</button>
</div>
</div>
)}
Expand Down Expand Up @@ -414,6 +440,7 @@ export function Onboarding() {
</div>
</div>
<AdvancedLLMSection values={advancedValues} setValues={setAdvancedValues} />
{flag("tinker") && (
<div className="model-row">
<span className="model-row-label">Tinker <span className="optional-tag">optional</span></span>
<div className="model-row-fields">
Expand All @@ -429,6 +456,7 @@ export function Onboarding() {
</div>
</div>
</div>
)}
<div className="model-row">
<span className="model-row-label">W&amp;B <span className="optional-tag">optional</span></span>
<div className="model-row-fields">
Expand All @@ -442,7 +470,7 @@ export function Onboarding() {
</div>
<div className="btn-row">
<button className="btn btn-ghost" onClick={() => setStep(2)}>Back</button>
<button className="btn btn-primary" disabled={!model.trim() || !geminiKey.trim() || !!tinkerError} onClick={handleSubmit}>Finish Setup</button>
<button className="btn btn-primary" disabled={!model.trim() || !geminiKey.trim() || (flag("tinker") && !!tinkerError)} onClick={handleSubmit}>Finish Setup</button>
</div>
</div>
)}
Expand Down
2 changes: 1 addition & 1 deletion src/client/renderer/components/overlay/Overlay.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useRef, useState } from "react";
import React, { useEffect, useRef, useState } from "react";

type OverlayStatus = "waiting" | "flushing" | "predicted";

Expand Down
3 changes: 1 addition & 2 deletions src/client/renderer/components/shared/AdvancedLLMSection.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { useState } from "react";
import React from "react";
import React, { useState } from "react";
import { ModelDropdown, LLM_MODELS, ModelOption } from "./ModelDropdown";

export const ADVANCED_ROWS: { label: string; modelKey: string; apiKeyKey: string }[] = [
Expand Down
3 changes: 1 addition & 2 deletions src/client/renderer/components/shared/CollapsibleSection.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { useState } from "react";
import React from "react";
import React, { useState } from "react";

interface Props {
title: string;
Expand Down
15 changes: 6 additions & 9 deletions src/client/renderer/components/shared/ModelDropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
import { useState, useEffect, useRef } from "react";
import React, { useState, useEffect, useRef } from "react";

export interface ModelOption {
value: string;
label: string;
}

export const LLM_MODELS: ModelOption[] = [
{ value: "gemini/gemini-3-flash-preview", label: "Gemini 3 Flash Preview" },
{ value: "gemini/gemini-3.1-flash-lite-preview", label: "Gemini 3.1 Flash-Lite Preview" },
{ value: "anthropic/claude-haiku-4-5", label: "Claude Haiku 4.5" },
{ value: "anthropic/claude-sonnet-4-6", label: "Claude Sonnet 4.6" },
{ value: "anthropic/claude-opus-4-6", label: "Claude Opus 4.6" },
{ value: "openai/gpt-5.2-nano", label: "OpenAI GPT-5.2 Nano" },
{ value: "openai/gpt-4.1-nano", label: "OpenAI GPT-4.1 Nano" },
];

export const TADA_MODELS: ModelOption[] = [
{ value: "gemini/gemini-3.1-flash-lite-preview", label: "Gemini 3.1 Flash-Lite Preview" },
{ value: "anthropic/claude-haiku-4-5", label: "Claude Haiku 4.5" },
{ value: "anthropic/claude-sonnet-4-6", label: "Claude Sonnet 4.6" },
{ value: "anthropic/claude-opus-4-6", label: "Claude Opus 4.6" },
{ value: "gemini/gemini-3.1-flash-lite-preview", label: "Gemini 3.1 Flash-Lite Preview" },
{ value: "openai/gpt-5.2-nano", label: "OpenAI GPT-5.2 Nano" },
{ value: "openai/gpt-4.1-nano", label: "OpenAI GPT-4.1 Nano" },
];

export const TINKER_MODELS: ModelOption[] = [
Expand Down
6 changes: 4 additions & 2 deletions src/client/renderer/components/views/ConnectorsView.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useEffect, useState, useRef } from "react";
import React, { useEffect, useState, useRef } from "react";
import { useAppContext, HistoryItem } from "../../context/AppContext";
import { useFeatureFlags, getFlag } from "../../featureFlags";
import * as api from "../../api/client";
import { useConnectors } from "../../hooks/useConnectors";
import { ConnectorItem, CONNECTOR_META } from "../connectors/ConnectorItem";
Expand All @@ -12,6 +13,7 @@ const BADGE_CLASS: Record<HistoryItem["type"], string> = {

export function ConnectorsView() {
const { state, dispatch } = useAppContext();
const featureFlags = useFeatureFlags();
const { connectors, loading, load, toggle, toggling, connectGoogle, connectOutlook, retry } = useConnectors();
const [connectingName, setConnectingName] = useState<string | null>(null);
const pollRef = useRef<ReturnType<typeof setInterval> | null>(null);
Expand Down Expand Up @@ -71,7 +73,7 @@ export function ConnectorsView() {
) : (
<div style={{ display: "flex", flexDirection: "column", gap: 2 }}>
{Object.entries(connectors)
.filter(([name]) => CONNECTOR_META[name])
.filter(([name]) => CONNECTOR_META[name] && getFlag(featureFlags, `connector_${name}`))
.sort(([a], [b]) => {
const keys = Object.keys(CONNECTOR_META);
return keys.indexOf(a) - keys.indexOf(b);
Expand Down
6 changes: 3 additions & 3 deletions src/client/renderer/components/views/DashboardView.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useEffect } from "react";
import React, { useEffect } from "react";
import { useAppContext } from "../../context/AppContext";
import { useTraining } from "../../hooks/useTraining";
import { requestPrediction } from "../../api/client";
import { TrainingTile, InferenceTile } from "../dashboard/PipelineTile";
import { PredictionCard } from "../dashboard/PredictionCard";
import { RewardsChart } from "../dashboard/RewardsChart";
Expand All @@ -26,8 +27,7 @@ export function DashboardView() {

const handleGenerate = async () => {
dispatch({ type: "PREDICTION_REQUESTED" });
await window.tada.startInference();
await window.tada.requestPrediction();
await requestPrediction();
};

return (
Expand Down
Loading