-
Notifications
You must be signed in to change notification settings - Fork 1
Null Library: Complete rebrand + NullProxy Engine (OAuth-spoofed AI proxy) #19
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
base: main
Are you sure you want to change the base?
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,18 @@ | ||
| # Null Library — Environment Variables | ||
| # Copy this file to .env and fill in your values. | ||
| # .env is gitignored and never committed. | ||
|
|
||
| # ── Free Gemini API Key (fallback) ────────────────────────────────────────── | ||
| # From https://aistudio.google.com/apikey — free tier available | ||
| # VITE_GOOGLE_API_KEY=AIza... | ||
|
|
||
| # ── NullProxy Engine: OAuth Provider Credentials ──────────────────────────── | ||
| # These are needed to use the OAuth proxy feature (requires desktop app). | ||
| # Register at https://console.cloud.google.com → APIs & Services → Credentials | ||
| # Redirect URI to add: http://localhost:8085/callback | ||
|
|
||
| # GEMINI_CLI_CLIENT_ID=your-client-id.apps.googleusercontent.com | ||
| # GEMINI_CLI_CLIENT_SECRET=your-client-secret | ||
|
|
||
| # GEMINI_ANTIGRAVITY_CLIENT_ID=your-second-client-id.apps.googleusercontent.com | ||
| # GEMINI_ANTIGRAVITY_CLIENT_SECRET=your-second-client-secret |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,3 +22,8 @@ dist-ssr | |
| *.njsproj | ||
| *.sln | ||
| *.sw? | ||
|
|
||
| # Environment files (contain secrets) | ||
| .env | ||
| .env.local | ||
| .env.*.local | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,6 @@ | ||
|
|
||
| import React, { useState, useCallback, useEffect, useRef } from 'react'; | ||
| import { AppStep, BookOutline, Chapter, MarketReport, AuthorProfile, GenreSuggestion, TopicSuggestion, KdpMarketingInfo, AppMode, BatchProject, KdpAutomationPayload } from './types'; | ||
| import { AppStep, BookOutline, Chapter, MarketReport, AuthorProfile, GenreSuggestion, TopicSuggestion, KdpMarketingInfo, AppMode, BatchProject, KdpAutomationPayload, ProxySettings } from './types'; | ||
| import * as geminiService from './services/geminiService'; | ||
| import * as realMarketService from './services/realMarketService'; | ||
| import * as storageService from './services/storageService'; | ||
|
|
@@ -12,17 +12,20 @@ import OutlineStep from './components/steps/OutlineStep'; | |
| import ContentGenerationStep from './components/steps/ContentGenerationStep'; | ||
| import IllustrationStep from './components/steps/IllustrationStep'; | ||
| import ReviewStep from './components/steps/ReviewStep'; | ||
| import SetupWizardStep from './components/steps/SetupWizardStep'; | ||
| import ProxySettingsModal from './components/ProxySettingsModal'; | ||
| import LoadingSpinner from './components/shared/LoadingSpinner'; | ||
| import { SparklesIcon, UserCircleIcon, TrashIcon, RocketLaunchIcon, CheckBadgeIcon } from './components/icons'; | ||
| import NullLibraryLogo from './components/NullLibraryLogo'; | ||
| import AuthorProfileModal from './components/AuthorProfileModal'; | ||
| import BatchMode from './components/BatchMode'; | ||
| import KdpAutomationBot from './components/KdpAutomationBot'; | ||
| import TitleBar from './components/TitleBar'; | ||
| import desktopBridge from './services/desktopBridge'; | ||
|
|
||
|
|
||
| import { useAutoSave } from './hooks/useAutoSave'; | ||
| import AutoSaveIndicator from './components/shared/AutoSaveIndicator'; | ||
| import { isFirstRun } from './services/oauthSetupService'; | ||
|
|
||
| const pageRangeToChapterCount = (pageRange: string): number => { | ||
| const firstNum = parseInt(pageRange.split('-')[0], 10); | ||
|
|
@@ -35,14 +38,18 @@ const pageRangeToChapterCount = (pageRange: string): number => { | |
| return 25; | ||
| }; | ||
|
|
||
| // Versioned storage key | ||
| const STORAGE_KEY = 'kdp-ai-booksmith-v5-db'; | ||
| // Versioned storage key (renamed to null-library from kdp-ai-booksmith) | ||
| const STORAGE_KEY = 'null-library-v1-db'; | ||
|
|
||
| function App() { | ||
| const [mode, setMode] = useState<AppMode>(AppMode.Single); | ||
| const [currentStep, setCurrentStep] = useState<AppStep>(AppStep.MarketResearch); | ||
| const [isLoading, setIsLoading] = useState(true); // Start true to allow DB load | ||
| const [error, setError] = useState<string | null>(null); | ||
|
|
||
| // ── First-run wizard state ──────────────────────────────────────────────── | ||
| const [showWizard, setShowWizard] = useState(false); | ||
| const [isProxySettingsOpen, setIsProxySettingsOpen] = useState(false); | ||
|
|
||
| // SINGLE BOOK MODE STATE | ||
| const [genreSuggestions, setGenreSuggestions] = useState<GenreSuggestion[] | null>(null); | ||
|
|
@@ -76,6 +83,7 @@ function App() { | |
| const [batchProjects, setBatchProjects] = useState<BatchProject[]>([]); | ||
| const [isBatchRunning, setIsBatchRunning] = useState(false); | ||
|
|
||
|
|
||
| // --- AUTO SAVE --- | ||
| const { lastSaved, isSaving: isAutoSaving } = useAutoSave({ | ||
| mode, | ||
|
|
@@ -133,20 +141,28 @@ function App() { | |
| } | ||
| } catch (e) { | ||
| console.error("Failed to load state from DB", e); | ||
| // Fallback: Check localStorage for migration | ||
| const legacyState = localStorage.getItem('kdp-ai-booksmith-v4'); | ||
| if (legacyState) { | ||
| console.log("Migrating from legacy localStorage..."); | ||
| try { | ||
| const parsed = JSON.parse(legacyState); | ||
| setAuthorProfile(parsed.authorProfile); | ||
| // We don't load everything to avoid bugs, just profile is useful | ||
| } catch(err) {} | ||
| // Fallback: Check localStorage for migration from legacy storage keys | ||
| const legacyKeys = ['kdp-ai-booksmith-v4', 'kdp-ai-booksmith-v5-db']; | ||
| for (const key of legacyKeys) { | ||
| const legacyState = localStorage.getItem(key); | ||
| if (legacyState) { | ||
| console.log("Migrating from legacy localStorage key:", key); | ||
| try { | ||
| const parsed = JSON.parse(legacyState); | ||
| setAuthorProfile(parsed.authorProfile); | ||
| } catch(err) {} | ||
| break; | ||
| } | ||
| } | ||
| } finally { | ||
| setIsLoading(false); | ||
| } | ||
|
|
||
| // Show first-run wizard if not completed | ||
| if (isFirstRun()) { | ||
| setShowWizard(true); | ||
| } | ||
|
|
||
| // Check storage status | ||
| const isPersisted = await navigator.storage && navigator.storage.persisted ? await navigator.storage.persisted() : false; | ||
| setIsPersistentStorage(isPersisted); | ||
|
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. [Contextual Comment] P1 | Confidence: High The PR introduces a new Code Suggestion: Evidence: symbol:geminiService.generateBookOutline, path:services/geminiService.ts [Contextual Comment] P1 | Confidence: High Consistent with the previous finding, the chapter generation still uses the legacy Code Suggestion: Evidence: symbol:geminiService.generateChapterContent, path:services/geminiService.ts |
||
|
|
@@ -796,7 +812,7 @@ function App() { | |
| chapterLoadingStates: {} // Don't save loading states | ||
|
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. [Contextual Comment] P2 | Confidence: Medium Speculative: The humanization logic was refactored from a sequential Code Suggestion: Evidence: method:handleHumanizeBook |
||
| }; | ||
|
|
||
| const fileName = bookOutline?.title ? `${bookOutline.title.replace(/[^a-z0-9]/gi, '_').toLowerCase()}.json` : 'kdp-project.json'; | ||
| const fileName = bookOutline?.title ? `${bookOutline.title.replace(/[^a-z0-9]/gi, '_').toLowerCase()}.json` : 'null-library-project.json'; | ||
|
|
||
| try { | ||
| const result = await desktopBridge.saveFile(JSON.stringify(projectState, null, 2), fileName); | ||
|
|
@@ -940,21 +956,36 @@ function App() { | |
|
|
||
| return ( | ||
| <div className="pt-8 min-h-screen flex flex-col bg-slate-900 text-slate-200 font-sans selection:bg-indigo-500 selection:text-white"> | ||
| <TitleBar onSave={handleSaveProject} onLoad={handleLoadProject} /> | ||
| <TitleBar onSave={handleSaveProject} onLoad={handleLoadProject} onOpenSettings={() => setIsProxySettingsOpen(true)} /> | ||
|
|
||
| {/* Setup Wizard overlay — shows on first launch */} | ||
| {showWizard && ( | ||
| <div className="fixed inset-0 z-[200]"> | ||
| <SetupWizardStep | ||
| onComplete={(_settings: ProxySettings) => setShowWizard(false)} | ||
| onSkip={() => setShowWizard(false)} | ||
| /> | ||
| </div> | ||
| )} | ||
|
|
||
| {/* Proxy Settings Modal */} | ||
| {isProxySettingsOpen && ( | ||
| <ProxySettingsModal onClose={() => setIsProxySettingsOpen(false)} /> | ||
| )} | ||
|
|
||
| {/* Header */} | ||
| <header className="border-b border-slate-800 bg-slate-900/50 backdrop-blur-sm sticky top-8 z-40"> | ||
| <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4"> | ||
| <div className="flex items-center justify-between"> | ||
| <div className="flex items-center gap-3"> | ||
| <div className="bg-gradient-to-br from-violet-600 to-indigo-600 p-2 rounded-lg shadow-lg shadow-violet-900/20"> | ||
| <SparklesIcon className="w-6 h-6 text-white" /> | ||
| <NullLibraryLogo size={24} /> | ||
| </div> | ||
| <div> | ||
| <h1 className="text-xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-white to-slate-400"> | ||
| KDP E-Book Generator | ||
| Null Library | ||
| </h1> | ||
| <p className="text-xs text-slate-500 font-medium tracking-wide">AI-POWERED PUBLISHING ENGINE</p> | ||
| <p className="text-xs text-slate-500 font-medium tracking-wide">THE ART OF INFINITE PRODUCTION</p> | ||
| </div> | ||
| </div> | ||
|
|
||
|
|
@@ -1062,7 +1093,15 @@ function App() { | |
| {isHighPerformanceMode ? "High Concurrency" : "Sequential"} | ||
| </button> | ||
| </div> | ||
| <span>App v1.5.0</span> | ||
| <span>App v3.0.0</span> | ||
| <button | ||
| onClick={() => setIsProxySettingsOpen(true)} | ||
| className="flex items-center gap-1 text-violet-400 hover:text-violet-300 transition-colors hover:underline" | ||
| title="Open NullProxy Engine settings" | ||
| > | ||
| <span>⚡</span> | ||
| <span>NullProxy</span> | ||
| </button> | ||
| </div> | ||
| </div> | ||
| </div> | ||
|
|
||
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.
P2 | Confidence: High
The
.gitignoreupdate simplifies the pattern but removes the explicit negation!.env.example. This is fine because.env.exampleis not matched by the new patterns (.env,.env.local,.env.*.local). However, the comment "Environment files (contain secrets)" is less emphatic than "NEVER commit real keys." The change is functionally correct but could be slightly improved for security awareness.Evidence: path:.gitignore