mirror of
https://github.com/waynesutton/markdown-site.git
synced 2026-01-12 04:09:14 +00:00
feat: Make semantic search optional and disabled by default
- Add SemanticSearchConfig interface with enabled toggle to siteConfig.ts - Default semantic search to disabled (enabled: false) to avoid blocking forks without OPENAI_API_KEY - Update SearchModal.tsx to conditionally show mode toggle based on config - Update sync-posts.ts to skip embedding generation when disabled - Add semantic search toggle to Dashboard config generator - Update FORK_CONFIG.md with Semantic Search Configuration section - Update fork-config.json.example with semanticSearch option - Update docs-semantic-search.md with enable/disable instructions - Update changelog and documentation When disabled (default): - Search modal shows only keyword search (no mode toggle) - Embedding generation skipped during sync - No OpenAI API key required 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,7 @@ import {
|
||||
TextAa,
|
||||
Brain,
|
||||
} from "@phosphor-icons/react";
|
||||
import { siteConfig } from "../config/siteConfig";
|
||||
|
||||
interface SearchModalProps {
|
||||
isOpen: boolean;
|
||||
@@ -31,6 +32,9 @@ interface SearchResult {
|
||||
}
|
||||
|
||||
export default function SearchModal({ isOpen, onClose }: SearchModalProps) {
|
||||
// Check if semantic search is enabled in siteConfig
|
||||
const semanticEnabled = siteConfig.semanticSearch?.enabled !== false;
|
||||
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [selectedIndex, setSelectedIndex] = useState(0);
|
||||
const [searchMode, setSearchMode] = useState<SearchMode>("keyword");
|
||||
@@ -100,8 +104,8 @@ export default function SearchModal({ isOpen, onClose }: SearchModalProps) {
|
||||
// Handle keyboard navigation
|
||||
const handleKeyDown = useCallback(
|
||||
(e: React.KeyboardEvent) => {
|
||||
// Tab toggles between search modes
|
||||
if (e.key === "Tab") {
|
||||
// Tab toggles between search modes (only if semantic search is enabled)
|
||||
if (e.key === "Tab" && semanticEnabled) {
|
||||
e.preventDefault();
|
||||
setSearchMode((prev) => (prev === "keyword" ? "semantic" : "keyword"));
|
||||
return;
|
||||
@@ -142,7 +146,7 @@ export default function SearchModal({ isOpen, onClose }: SearchModalProps) {
|
||||
break;
|
||||
}
|
||||
},
|
||||
[results, selectedIndex, navigate, onClose, searchMode, searchQuery]
|
||||
[results, selectedIndex, navigate, onClose, searchMode, searchQuery, semanticEnabled]
|
||||
);
|
||||
|
||||
// Handle clicking on a result
|
||||
@@ -168,25 +172,27 @@ export default function SearchModal({ isOpen, onClose }: SearchModalProps) {
|
||||
return (
|
||||
<div className="search-modal-backdrop" onClick={handleBackdropClick}>
|
||||
<div className="search-modal">
|
||||
{/* Search mode toggle */}
|
||||
<div className="search-mode-toggle">
|
||||
<button
|
||||
className={`search-mode-btn ${searchMode === "keyword" ? "active" : ""}`}
|
||||
onClick={() => setSearchMode("keyword")}
|
||||
title="Keyword search - matches exact words"
|
||||
>
|
||||
<TextAa size={16} weight="bold" />
|
||||
<span>Keyword</span>
|
||||
</button>
|
||||
<button
|
||||
className={`search-mode-btn ${searchMode === "semantic" ? "active" : ""}`}
|
||||
onClick={() => setSearchMode("semantic")}
|
||||
title="Semantic search - finds similar meaning"
|
||||
>
|
||||
<Brain size={16} weight="bold" />
|
||||
<span>Semantic</span>
|
||||
</button>
|
||||
</div>
|
||||
{/* Search mode toggle - only shown when semantic search is enabled */}
|
||||
{semanticEnabled && (
|
||||
<div className="search-mode-toggle">
|
||||
<button
|
||||
className={`search-mode-btn ${searchMode === "keyword" ? "active" : ""}`}
|
||||
onClick={() => setSearchMode("keyword")}
|
||||
title="Keyword search - matches exact words"
|
||||
>
|
||||
<TextAa size={16} weight="bold" />
|
||||
<span>Keyword</span>
|
||||
</button>
|
||||
<button
|
||||
className={`search-mode-btn ${searchMode === "semantic" ? "active" : ""}`}
|
||||
onClick={() => setSearchMode("semantic")}
|
||||
title="Semantic search - finds similar meaning"
|
||||
>
|
||||
<Brain size={16} weight="bold" />
|
||||
<span>Semantic</span>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Search input */}
|
||||
<div className="search-modal-input-wrapper">
|
||||
@@ -223,9 +229,11 @@ export default function SearchModal({ isOpen, onClose }: SearchModalProps) {
|
||||
: "Describe what you're looking for"}
|
||||
</p>
|
||||
<div className="search-modal-shortcuts">
|
||||
<span className="search-shortcut">
|
||||
<kbd>Tab</kbd> Switch mode
|
||||
</span>
|
||||
{semanticEnabled && (
|
||||
<span className="search-shortcut">
|
||||
<kbd>Tab</kbd> Switch mode
|
||||
</span>
|
||||
)}
|
||||
<span className="search-shortcut">
|
||||
<kbd>↑</kbd>
|
||||
<kbd>↓</kbd> Navigate
|
||||
@@ -292,9 +300,11 @@ export default function SearchModal({ isOpen, onClose }: SearchModalProps) {
|
||||
{/* Footer with keyboard hints */}
|
||||
{results && results.length > 0 && (
|
||||
<div className="search-modal-footer">
|
||||
<span className="search-footer-hint">
|
||||
<kbd>Tab</kbd> switch mode
|
||||
</span>
|
||||
{semanticEnabled && (
|
||||
<span className="search-footer-hint">
|
||||
<kbd>Tab</kbd> switch mode
|
||||
</span>
|
||||
)}
|
||||
<span className="search-footer-hint">
|
||||
<kbd>↵</kbd> select
|
||||
</span>
|
||||
|
||||
@@ -236,6 +236,13 @@ export interface ImageLightboxConfig {
|
||||
enabled: boolean; // Global toggle for image lightbox feature
|
||||
}
|
||||
|
||||
// Semantic search configuration
|
||||
// Enables AI-powered search using vector embeddings
|
||||
// Requires OPENAI_API_KEY environment variable in Convex dashboard
|
||||
export interface SemanticSearchConfig {
|
||||
enabled: boolean; // Global toggle for semantic search feature
|
||||
}
|
||||
|
||||
// Social link configuration for social footer
|
||||
export interface SocialLink {
|
||||
platform:
|
||||
@@ -365,6 +372,9 @@ export interface SiteConfig {
|
||||
|
||||
// AI Dashboard configuration (optional)
|
||||
aiDashboard?: AIDashboardConfig;
|
||||
|
||||
// Semantic search configuration (optional)
|
||||
semanticSearch?: SemanticSearchConfig;
|
||||
}
|
||||
|
||||
// Default site configuration
|
||||
@@ -744,6 +754,13 @@ export const siteConfig: SiteConfig = {
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// Semantic search configuration
|
||||
// Set enabled: true to enable semantic search (requires OPENAI_API_KEY in Convex)
|
||||
// When disabled, only keyword search is available (no API key needed)
|
||||
semanticSearch: {
|
||||
enabled: false, // Set to true to enable semantic search (requires OPENAI_API_KEY)
|
||||
},
|
||||
};
|
||||
|
||||
// Export the config as default for easy importing
|
||||
|
||||
@@ -4597,6 +4597,8 @@ function ConfigSection({
|
||||
mcpServerRequireAuth: siteConfig.mcpServer?.requireAuth || false,
|
||||
// Image lightbox
|
||||
imageLightboxEnabled: siteConfig.imageLightbox?.enabled !== false,
|
||||
// Semantic search
|
||||
semanticSearchEnabled: siteConfig.semanticSearch?.enabled || false,
|
||||
});
|
||||
|
||||
const [copied, setCopied] = useState(false);
|
||||
@@ -4762,6 +4764,12 @@ export const siteConfig: SiteConfig = {
|
||||
imageLightbox: {
|
||||
enabled: ${config.imageLightboxEnabled},
|
||||
},
|
||||
|
||||
// Semantic search configuration
|
||||
// Set enabled: true to enable AI-powered semantic search (requires OPENAI_API_KEY in Convex)
|
||||
semanticSearch: {
|
||||
enabled: ${config.semanticSearchEnabled},
|
||||
},
|
||||
};
|
||||
|
||||
export default siteConfig;
|
||||
@@ -5573,6 +5581,26 @@ export default siteConfig;
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Semantic Search */}
|
||||
<div className="dashboard-config-card">
|
||||
<h3>Semantic Search</h3>
|
||||
<div className="config-field checkbox">
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={config.semanticSearchEnabled}
|
||||
onChange={(e) =>
|
||||
handleChange("semanticSearchEnabled", e.target.checked)
|
||||
}
|
||||
/>
|
||||
<span>Enable semantic search (requires OPENAI_API_KEY in Convex)</span>
|
||||
</label>
|
||||
</div>
|
||||
<p className="config-hint">
|
||||
When enabled, search modal shows both Keyword and Semantic modes. Requires OpenAI API key for embeddings.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Links */}
|
||||
<div className="dashboard-config-card">
|
||||
<h3>External Links</h3>
|
||||
|
||||
Reference in New Issue
Block a user