+
{
setElapsed(Date.now() - start);
@@ -33,14 +33,24 @@ const TimeDisplay = ({ start, end, status }: { start?: number, end?: number, sta
const seconds = (elapsed / 1000).toFixed(1);
return (
-
+
{seconds}s
);
};
+// Helper to extract the last meaningful line of thought
+const getLastThought = (text?: string) => {
+ if (!text) return "";
+ // Clean up markdown markers broadly
+ const cleanText = text.replace(/[*_#`]/g, '');
+ const lines = cleanText.split('\n').map(l => l.trim()).filter(l => l.length > 0);
+ return lines.length ? lines[lines.length - 1] : "";
+};
+
const ExpertCard = ({ expert }: { expert: ExpertResult }) => {
+ const [isExpanded, setIsExpanded] = useState(false);
const [view, setView] = useState<'thoughts' | 'output'>('output');
const isWorking = expert.status === 'thinking';
@@ -50,7 +60,7 @@ const ExpertCard = ({ expert }: { expert: ExpertResult }) => {
const round = expert.round || 1;
// Auto-switch to thoughts if that's all we have so far
- React.useEffect(() => {
+ useEffect(() => {
if (isWorking && expert.thoughts && !expert.content) {
setView('thoughts');
} else if (expert.content && view === 'thoughts' && !expert.thoughts) {
@@ -58,115 +68,187 @@ const ExpertCard = ({ expert }: { expert: ExpertResult }) => {
}
}, [expert.thoughts, expert.content, isWorking]);
- return (
-
- {/* Header */}
-
-
-
-
-
-
-
+ // Determine border/bg styles based on status
+ const getStatusStyles = () => {
+ if (isWorking) return 'border-indigo-300 bg-white shadow-md shadow-indigo-100 ring-1 ring-indigo-50';
+ if (isDone) return 'border-teal-200 bg-white hover:border-teal-300 hover:shadow-md';
+ if (isError) return 'border-red-200 bg-red-50/30';
+ return 'border-slate-200 bg-slate-50 opacity-60 hover:opacity-100';
+ };
+
+ const currentThought = getLastThought(expert.thoughts);
+
+ const ExpandedModal = (
+
-
-
- {/* Timer for Expert */}
- {expert.role}
- {round > 1 && ( -
-
- Round {round}
-
- )}
-
+ {/* Backdrop */}
+
setIsExpanded(false)} />
+
+ {/* Modal Content */}
+
+
+ {/* Modal Header */}
+
- {/* Tabs */}
- {!isPending && (
-
+
-
+
-
+
+
+
+
+ {expert.role} + {round > 1 && R{round}} +
+
+
+ •
+
+ {isWorking ? 'Thinking...' : expert.status}
+
+
+
-
- {expert.description}
- {expert.temperature !== undefined && ( -
-
- {expert.temperature}
-
- )}
-
- {isWorking && }
- {isDone && }
- {isError && }
+
-
+ {/* Tabs */}
+
- )}
-
- {/* Content Area */}
-
- {isPending ? (
-
-
- Waiting for assignment...
-
- ) : (
- <>
+
+ {/* Scrollable Content */}
+
{view === 'thoughts' && (
-
);
+
+ return (
+ <>
+ {/* Collapsed Widget View */}
+
+
{expert.thoughts ? (
) : (
- Initializing thought process...
+
)}
{view === 'output' && (
- No reasoning thoughts recorded.
)}
- {isWorking && }
+ {isWorking && expert.thoughts && }
+
+
+ {/* Footer */}
+ {expert.description && (
+
{expert.content ? (
) : (
-
- {isWorking ? "Formulating output..." : "No output generated."}
-
+
)}
- >
+ No output generated yet.
)}
- {isWorking && !expert.content && }
+ {isWorking && !expert.content && }
+ Goal: {expert.description}
+
)}
!isPending && setIsExpanded(true)}
+ className={`
+ group relative flex flex-col p-3 rounded-xl border transition-all duration-300 cursor-pointer
+ ${getStatusStyles()}
+ ${!isPending ? 'active:scale-[0.98]' : 'cursor-default'}
+ `}
+ >
+ {/* Top Row: Icon, Role, Timer */}
+
+
+ {/* Render Portal for Expanded View */}
+ {isExpanded && createPortal(ExpandedModal, document.body)}
+ >
+ );
};
-export default ExpertCard;
\ No newline at end of file
+export default ExpertCard;
diff --git a/prisma/ProcessNode.tsx b/prisma/ProcessNode.tsx
index 2257b8f..50c429e 100644
--- a/prisma/ProcessNode.tsx
+++ b/prisma/ProcessNode.tsx
@@ -22,24 +22,24 @@ const ProcessNode = ({
const isCompleted = status === 'completed';
return (
-
+
+
+
+ {/* Bottom Row: Rolling Thought / Activity Indicator */}
+ {!isPending && (
+
+ {isWorking ? :
+ (isDone ? :
+ (isError ? : ))
+ }
+
+
+
+
+
+
+
+
+ {/* Subtext: Status or Description */}
+ + {expert.role} +
+ {round > 1 && R{round}} +
+ {isPending ? 'Waiting for assignment...' : expert.description}
+
+
+ {/* Rolling animation container */}
+
+ )}
+
+ {isWorking && }
+
+ {isWorking && !currentThought ? "Initializing..." : (currentThought || "View details...")}
+
+
+
+ {/* Hover hint */}
+
+
+
+
+
{children && (
@@ -50,7 +50,7 @@ const ProcessNode = ({
{isExpanded && children && (
-
{isActive ? : (isCompleted ? : )}
-
+
+
{title}
- {isActive &&
Processing...
} + {isActive &&Processing...
}
+
{children}
)}
diff --git a/prisma/SettingsModal.tsx b/prisma/SettingsModal.tsx
index bf5d4c1..a1fb650 100644
--- a/prisma/SettingsModal.tsx
+++ b/prisma/SettingsModal.tsx
@@ -26,30 +26,33 @@ const SettingsModal = ({
if (!isOpen) return null;
return (
-
-
+
+
{/* Header */}
-
-
-
-
Configuration
+
+
{/* Body */}
-
+
-
+
+
+ Configuration
+
-
-
+
+
+
@@ -57,10 +60,10 @@ const SettingsModal = ({
{/* Footer */}
-
+
Done
diff --git a/prisma/components/ChatArea.tsx b/prisma/components/ChatArea.tsx
index a13178d..9d5fe5d 100644
--- a/prisma/components/ChatArea.tsx
+++ b/prisma/components/ChatArea.tsx
@@ -29,15 +29,22 @@ const ChatArea = ({
return (
{isIdle ? (
-
-
-
Prisma
-- Deep multi-agent reasoning. +
+
) : (
-
+
+
+
+ + Prisma + .ai +
+
+ Deep multi-agent reasoning engine.
+ Visualize the thought process in real-time.
+
{/* History */}
{messages.map((msg, idx) => (
-
-
-
+
+
+
+
-
Prisma
+
+ Prisma
+ Thinking
+
{/* Active Thinking Process */}
-
+
+
{
};
return (
-
-
+
+
{/* Avatar */}
-
{/* Content */}
-
{isUser ? (
-
+
) : (
-
+
)}
-
-
+
+
+
{copied ? (
<>
-
- Copied
+
+ Copied
>
) : (
@@ -73,26 +82,30 @@ const ChatMessageItem = ({ message, isLast }: ChatMessageProps) => {
{/* Thinking Process Accordion (Only for AI) */}
{!isUser && hasThinkingData && (
-
{isUser ? 'You' : 'Prisma'}
+ {!isUser && (
+
+ AI
+
+ )}
{message.content && (
+
setShowThinking(!showThinking)}
- className="flex items-center gap-2 text-xs font-medium text-slate-500 hover:text-slate-800 bg-slate-50 hover:bg-slate-100 border border-slate-200 rounded-lg px-3 py-2 transition-colors w-full md:w-auto"
+ className="group/btn flex items-center gap-3 text-xs font-semibold text-slate-600 hover:text-slate-900 bg-white hover:bg-slate-50 border border-slate-200 hover:border-slate-300 rounded-xl px-4 py-2.5 transition-all w-full md:w-auto shadow-sm hover:shadow-md active:scale-[0.99]"
>
+ : }
+ |
+ {showThinking ? : }
{showThinking && (
-
+
+
{message.isThinking
- ? "Thinking..."
+ ? "Reasoning in progress..."
: (message.totalDuration
- ? `Thought for ${(message.totalDuration / 1000).toFixed(1)} seconds`
- : "Reasoning Process")
+ ? `Reasoning process (${(message.totalDuration / 1000).toFixed(1)}s)`
+ : "View reasoning process")
}
- {showThinking ?
+
{
{/* Attachments */}
{message.attachments && message.attachments.length > 0 && (
-
+
{message.attachments.map(att => (
window.open(att.url || `data:${att.mimeType};base64,${att.data}`, '_blank')}
/>
))}
@@ -118,23 +131,23 @@ const ChatMessageItem = ({ message, isLast }: ChatMessageProps) => {
)}
{/* Text Content */}
-
+
{message.content ? (
) : (
- message.isThinking &&
+ message.isThinking &&
)}
{/* Internal Monologue (Synthesis Thoughts) - Optional Footer */}
{!isUser && message.synthesisThoughts && (
-
+
-
-
-
+
+
Show Internal Monologue
-
+
{message.synthesisThoughts}
diff --git a/prisma/components/Header.tsx b/prisma/components/Header.tsx
index 7d8f197..3536e0a 100644
--- a/prisma/components/Header.tsx
+++ b/prisma/components/Header.tsx
@@ -18,49 +18,53 @@ const Header = ({ selectedModel, setSelectedModel, onOpenSettings, onToggleSideb
const availableModels = getAllModels(config);
return (
-
-
-
+
+
+
-
+
-
-
- Prisma
+
+
+
+
+
+ Prisma
+ .ai
-
-
+
+
-
+
-
+
diff --git a/prisma/components/InputSection.tsx b/prisma/components/InputSection.tsx
index ad94666..1ae834e 100644
--- a/prisma/components/InputSection.tsx
+++ b/prisma/components/InputSection.tsx
@@ -138,7 +138,7 @@ const InputSection = ({ query, setQuery, onRun, onStop, appState, focusTrigger }
)}
{/* Input Container */}
-
+
fileInputRef.current?.click()}
- className="flex-shrink-0 p-2.5 mb-0.5 ml-1 rounded-full text-slate-400 hover:text-slate-600 hover:bg-slate-100 transition-colors"
+ className="flex-shrink-0 p-3 mb-1 ml-1 rounded-full text-slate-400 hover:text-indigo-600 hover:bg-indigo-50/50 transition-all duration-200 group"
title="Attach Image"
disabled={isRunning}
>
-
+
-
+
{isRunning ? (
@@ -185,9 +185,9 @@ const InputSection = ({ query, setQuery, onRun, onStop, appState, focusTrigger }
-
+
)}
diff --git a/prisma/components/ProcessFlow.tsx b/prisma/components/ProcessFlow.tsx
index 294b955..b0e2183 100644
--- a/prisma/components/ProcessFlow.tsx
+++ b/prisma/components/ProcessFlow.tsx
@@ -38,8 +38,8 @@ const GlobalTimer = ({ start, end, appState }: { start: number | null | undefine
const seconds = (elapsed / 1000).toFixed(1);
return (
-
-
+
+
{seconds}s
);
@@ -54,16 +54,12 @@ const ProcessFlow = ({ appState, managerAnalysis, experts, defaultExpanded = tru
const isComplete = appState === 'completed';
// Experts are active if ANY expert is currently thinking or pending
- // We use this logic instead of just `appState` because now experts run IN PARALLEL with analysis
const hasExperts = experts.length > 0;
const anyExpertWorking = experts.some(e => e.status === 'thinking' || e.status === 'pending');
const allExpertsDone = experts.length > 0 && experts.every(e => e.status === 'completed' || e.status === 'error');
// Logic for Node Active States
- // 1. Manager: Active if analyzing, OR if we don't have analysis yet but experts have started (edge case), Completed if analysis exists.
const managerStatus = (appState === 'analyzing' && !managerAnalysis) ? 'active' : (isAnalysisDone ? 'completed' : 'idle');
-
- // 2. Experts: Active if any is working, Completed if all are done, Idle otherwise
const expertsStatus = anyExpertWorking ? 'active' : (allExpertsDone ? 'completed' : 'idle');
return (
@@ -100,7 +96,7 @@ const ProcessFlow = ({ appState, managerAnalysis, experts, defaultExpanded = tru
>
) : (
-
+
Analyzing request...
)}
@@ -116,7 +112,8 @@ const ProcessFlow = ({ appState, managerAnalysis, experts, defaultExpanded = tru
isExpanded={isExpanded}
onToggle={() => setIsExpanded(!isExpanded)}
>
-
+ {/* Modified: Compact Grid for Widgets */}
+
{experts.map((expert) => (
))}
@@ -136,11 +133,11 @@ const ProcessFlow = ({ appState, managerAnalysis, experts, defaultExpanded = tru
{isSynthesisActive ? (
-
+
Synthesizing final answer...
) : (
-
+
Reasoning complete.
diff --git a/prisma/components/Sidebar.tsx b/prisma/components/Sidebar.tsx
index eb4a223..0db8b98 100644
--- a/prisma/components/Sidebar.tsx
+++ b/prisma/components/Sidebar.tsx
@@ -1,5 +1,5 @@
import React from 'react';
-import { Plus, MessageSquare, Trash2, X, History } from 'lucide-react';
+import { Plus, MessageSquare, Trash2, X, History, MessageSquareText } from 'lucide-react';
import { ChatSession } from '../types';
interface SidebarProps {
@@ -34,19 +34,19 @@ const Sidebar = ({
{/* Sidebar Container */}
{/* Header */}
-
-
-
- History
+
+
+
+ Chat History
-
-
+
+
@@ -57,18 +57,21 @@ const Sidebar = ({
onNewChat();
if (window.innerWidth < 1024) onClose();
}}
- className="w-full flex items-center justify-center gap-2 bg-blue-600 hover:bg-blue-700 text-white py-2.5 px-4 rounded-lg transition-colors shadow-sm font-medium text-sm"
+ className="w-full flex items-center justify-center gap-2.5 bg-slate-900 hover:bg-slate-800 text-white py-3 px-4 rounded-xl transition-all shadow-lg shadow-slate-900/10 hover:shadow-xl hover:-translate-y-0.5 active:translate-y-0 active:shadow-md font-semibold text-sm group border border-slate-800"
>
-
- New Chat
+
+ New Thread
{/* Session List */}
-
+
{sessions.length === 0 ? (
-
- No chat history yet.
+
+
+
+
+ No chat history yet
) : (
sessions.map((session) => (
@@ -79,23 +82,33 @@ const Sidebar = ({
if (window.innerWidth < 1024) onClose();
}}
className={`
- group relative flex items-center gap-3 p-3 rounded-lg cursor-pointer transition-all
+ group relative flex items-center gap-3 p-3 rounded-xl cursor-pointer transition-all duration-200 border
${currentSessionId === session.id
- ? 'bg-white shadow-sm border border-slate-200 text-slate-900'
- : 'text-slate-600 hover:bg-slate-100 border border-transparent'}
+ ? 'bg-white shadow-sm border-slate-200 text-slate-900 z-10 ring-1 ring-slate-100'
+ : 'text-slate-600 hover:bg-white/60 hover:text-slate-900 border-transparent hover:border-slate-100 hover:shadow-sm'}
`}
>
-
-
- {session.title}
-
- {new Date(session.createdAt).toLocaleDateString()}
+
+
+
+
+
+
+ {session.title}
+
+
+ {new Date(session.createdAt).toLocaleDateString(undefined, { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' })}
onDeleteSession(session.id, e)}
- className="absolute right-2 top-1/2 -translate-y-1/2 p-1.5 rounded-md opacity-0 group-hover:opacity-100 hover:bg-red-50 hover:text-red-600 text-slate-400 transition-all"
+ className="absolute right-2 top-1/2 -translate-y-1/2 p-1.5 rounded-lg opacity-0 group-hover:opacity-100 hover:bg-red-50 hover:text-red-600 text-slate-300 transition-all scale-90 hover:scale-100 hover:shadow-sm"
title="Delete Chat"
>
diff --git a/prisma/components/settings/ThinkingSection.tsx b/prisma/components/settings/ThinkingSection.tsx
index b2513de..ca74398 100644
--- a/prisma/components/settings/ThinkingSection.tsx
+++ b/prisma/components/settings/ThinkingSection.tsx
@@ -1,6 +1,6 @@
import React from 'react';
-import { RefreshCw } from 'lucide-react';
+import { RefreshCw, Search } from 'lucide-react';
import { AppConfig, ModelOption } from '../../types';
import { getValidThinkingLevels } from '../../config';
import LevelSelect from './LevelSelect';
@@ -39,6 +39,25 @@ const ThinkingSection = ({ config, setConfig, model }: ThinkingSectionProps) =>
+
+
+
+
+ Web Search
+ Allow the model to use grounded web search when supported.
+
+
+
+
+
{
appendExperts
} = useDeepThinkState();
+ let enableWebSearch = false;
+
/**
* Orchestrates a single expert's lifecycle (Start -> Stream -> End)
*/
@@ -57,6 +59,7 @@ export const useDeepThink = () => {
context,
attachments,
budget,
+ enableWebSearch,
signal,
(textChunk, thoughtChunk) => {
fullContent += textChunk;
@@ -98,6 +101,7 @@ export const useDeepThink = () => {
if (abortControllerRef.current) abortControllerRef.current.abort();
abortControllerRef.current = new AbortController();
const signal = abortControllerRef.current.signal;
+ enableWebSearch = config.enableWebSearch ?? false;
logger.info('System', 'Starting DeepThink Process', { model, provider: getAIProvider(model) });
@@ -137,7 +141,7 @@ export const useDeepThink = () => {
query,
recentHistory,
currentAttachments,
- getThinkingBudget(config.planningLevel, model)
+ getThinkingBudget(config.planningLevel, model), enableWebSearch
);
const primaryExpert: ExpertResult = {
@@ -197,7 +201,7 @@ export const useDeepThink = () => {
const reviewResult = await executeManagerReview(
ai, model, query, expertsDataRef.current,
- getThinkingBudget(config.planningLevel, model)
+ getThinkingBudget(config.planningLevel, model), enableWebSearch
);
if (signal.aborted) return;
@@ -243,7 +247,7 @@ export const useDeepThink = () => {
await streamSynthesisResponse(
ai, model, query, recentHistory, expertsDataRef.current,
currentAttachments,
- getThinkingBudget(config.synthesisLevel, model), signal,
+ getThinkingBudget(config.synthesisLevel, model), enableWebSearch, signal,
(textChunk, thoughtChunk) => {
fullFinalText += textChunk;
fullFinalThoughts += thoughtChunk;
diff --git a/prisma/index.css b/prisma/index.css
index f968d47..aa44146 100644
--- a/prisma/index.css
+++ b/prisma/index.css
@@ -1 +1,31 @@
-/* Base styles are handled by Tailwind CSS */
+/* Animation utilities for modal/popover transitions */
+.animate-in {
+ animation-duration: 200ms;
+ animation-fill-mode: forwards;
+ animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
+}
+
+.fade-in { animation-name: fadeIn; }
+.zoom-in-95 { animation-name: zoomIn95; }
+.slide-in-from-top-2 { animation-name: slideInFromTop2; }
+
+@keyframes fadeIn {
+ from { opacity: 0; }
+ to { opacity: 1; }
+}
+
+@keyframes zoomIn95 {
+ from { opacity: 0; transform: scale(0.95); }
+ to { opacity: 1; transform: scale(1); }
+}
+
+@keyframes slideInFromTop2 {
+ from { opacity: 0; transform: translateY(-0.5rem); }
+ to { opacity: 1; transform: translateY(0); }
+}
+
+/* Scoped scrollbar for containers */
+.custom-scrollbar::-webkit-scrollbar { width: 6px; height: 6px; }
+.custom-scrollbar::-webkit-scrollbar-track { background: transparent; }
+.custom-scrollbar::-webkit-scrollbar-thumb { background-color: #cbd5e1; border-radius: 20px; }
+.custom-scrollbar::-webkit-scrollbar-thumb:hover { background-color: #94a3b8; }
diff --git a/prisma/index.html b/prisma/index.html
index 518e559..639b954 100644
--- a/prisma/index.html
+++ b/prisma/index.html
@@ -7,67 +7,58 @@
-
+
+
{message.synthesisThoughts}
diff --git a/prisma/components/Header.tsx b/prisma/components/Header.tsx
index 7d8f197..3536e0a 100644
--- a/prisma/components/Header.tsx
+++ b/prisma/components/Header.tsx
@@ -18,49 +18,53 @@ const Header = ({ selectedModel, setSelectedModel, onOpenSettings, onToggleSideb
const availableModels = getAllModels(config);
return (
-
-
+
+
+
-
+
-
-
-
- Prisma
+
- Prisma
+
+
+
+
+
+ Prisma
+ .ai
-
+
diff --git a/prisma/components/InputSection.tsx b/prisma/components/InputSection.tsx
index ad94666..1ae834e 100644
--- a/prisma/components/InputSection.tsx
+++ b/prisma/components/InputSection.tsx
@@ -138,7 +138,7 @@ const InputSection = ({ query, setQuery, onRun, onStop, appState, focusTrigger }
)}
{/* Input Container */}
-
+
-
+
-
+
+
fileInputRef.current?.click()}
- className="flex-shrink-0 p-2.5 mb-0.5 ml-1 rounded-full text-slate-400 hover:text-slate-600 hover:bg-slate-100 transition-colors"
+ className="flex-shrink-0 p-3 mb-1 ml-1 rounded-full text-slate-400 hover:text-indigo-600 hover:bg-indigo-50/50 transition-all duration-200 group"
title="Attach Image"
disabled={isRunning}
>
-
+
-
+
{isRunning ? (
@@ -185,9 +185,9 @@ const InputSection = ({ query, setQuery, onRun, onStop, appState, focusTrigger }
-
+
)}
diff --git a/prisma/components/ProcessFlow.tsx b/prisma/components/ProcessFlow.tsx
index 294b955..b0e2183 100644
--- a/prisma/components/ProcessFlow.tsx
+++ b/prisma/components/ProcessFlow.tsx
@@ -38,8 +38,8 @@ const GlobalTimer = ({ start, end, appState }: { start: number | null | undefine
const seconds = (elapsed / 1000).toFixed(1);
return (
-
-
+
+
{seconds}s
);
@@ -54,16 +54,12 @@ const ProcessFlow = ({ appState, managerAnalysis, experts, defaultExpanded = tru
const isComplete = appState === 'completed';
// Experts are active if ANY expert is currently thinking or pending
- // We use this logic instead of just `appState` because now experts run IN PARALLEL with analysis
const hasExperts = experts.length > 0;
const anyExpertWorking = experts.some(e => e.status === 'thinking' || e.status === 'pending');
const allExpertsDone = experts.length > 0 && experts.every(e => e.status === 'completed' || e.status === 'error');
// Logic for Node Active States
- // 1. Manager: Active if analyzing, OR if we don't have analysis yet but experts have started (edge case), Completed if analysis exists.
const managerStatus = (appState === 'analyzing' && !managerAnalysis) ? 'active' : (isAnalysisDone ? 'completed' : 'idle');
-
- // 2. Experts: Active if any is working, Completed if all are done, Idle otherwise
const expertsStatus = anyExpertWorking ? 'active' : (allExpertsDone ? 'completed' : 'idle');
return (
@@ -100,7 +96,7 @@ const ProcessFlow = ({ appState, managerAnalysis, experts, defaultExpanded = tru
>
) : (
-
+
Analyzing request...
)}
@@ -116,7 +112,8 @@ const ProcessFlow = ({ appState, managerAnalysis, experts, defaultExpanded = tru
isExpanded={isExpanded}
onToggle={() => setIsExpanded(!isExpanded)}
>
-
+ {/* Modified: Compact Grid for Widgets */}
+
{experts.map((expert) => (
))}
@@ -136,11 +133,11 @@ const ProcessFlow = ({ appState, managerAnalysis, experts, defaultExpanded = tru
{isSynthesisActive ? (
-
+
Synthesizing final answer...
) : (
-
+
Reasoning complete.
diff --git a/prisma/components/Sidebar.tsx b/prisma/components/Sidebar.tsx
index eb4a223..0db8b98 100644
--- a/prisma/components/Sidebar.tsx
+++ b/prisma/components/Sidebar.tsx
@@ -1,5 +1,5 @@
import React from 'react';
-import { Plus, MessageSquare, Trash2, X, History } from 'lucide-react';
+import { Plus, MessageSquare, Trash2, X, History, MessageSquareText } from 'lucide-react';
import { ChatSession } from '../types';
interface SidebarProps {
@@ -34,19 +34,19 @@ const Sidebar = ({
{/* Sidebar Container */}
{/* Header */}
-
-
-
- History
+
- New Chat
+
+ New Thread
{/* Session List */}
-
+
-
+
+
@@ -57,18 +57,21 @@ const Sidebar = ({
onNewChat();
if (window.innerWidth < 1024) onClose();
}}
- className="w-full flex items-center justify-center gap-2 bg-blue-600 hover:bg-blue-700 text-white py-2.5 px-4 rounded-lg transition-colors shadow-sm font-medium text-sm"
+ className="w-full flex items-center justify-center gap-2.5 bg-slate-900 hover:bg-slate-800 text-white py-3 px-4 rounded-xl transition-all shadow-lg shadow-slate-900/10 hover:shadow-xl hover:-translate-y-0.5 active:translate-y-0 active:shadow-md font-semibold text-sm group border border-slate-800"
>
-
+
+ Chat History
-
+
{sessions.length === 0 ? (
-
-
- {
appendExperts
} = useDeepThinkState();
+ let enableWebSearch = false;
+
/**
* Orchestrates a single expert's lifecycle (Start -> Stream -> End)
*/
@@ -57,6 +59,7 @@ export const useDeepThink = () => {
context,
attachments,
budget,
+ enableWebSearch,
signal,
(textChunk, thoughtChunk) => {
fullContent += textChunk;
@@ -98,6 +101,7 @@ export const useDeepThink = () => {
if (abortControllerRef.current) abortControllerRef.current.abort();
abortControllerRef.current = new AbortController();
const signal = abortControllerRef.current.signal;
+ enableWebSearch = config.enableWebSearch ?? false;
logger.info('System', 'Starting DeepThink Process', { model, provider: getAIProvider(model) });
@@ -137,7 +141,7 @@ export const useDeepThink = () => {
query,
recentHistory,
currentAttachments,
- getThinkingBudget(config.planningLevel, model)
+ getThinkingBudget(config.planningLevel, model), enableWebSearch
);
const primaryExpert: ExpertResult = {
@@ -197,7 +201,7 @@ export const useDeepThink = () => {
const reviewResult = await executeManagerReview(
ai, model, query, expertsDataRef.current,
- getThinkingBudget(config.planningLevel, model)
+ getThinkingBudget(config.planningLevel, model), enableWebSearch
);
if (signal.aborted) return;
@@ -243,7 +247,7 @@ export const useDeepThink = () => {
await streamSynthesisResponse(
ai, model, query, recentHistory, expertsDataRef.current,
currentAttachments,
- getThinkingBudget(config.synthesisLevel, model), signal,
+ getThinkingBudget(config.synthesisLevel, model), enableWebSearch, signal,
(textChunk, thoughtChunk) => {
fullFinalText += textChunk;
fullFinalThoughts += thoughtChunk;
diff --git a/prisma/index.css b/prisma/index.css
index f968d47..aa44146 100644
--- a/prisma/index.css
+++ b/prisma/index.css
@@ -1 +1,31 @@
-/* Base styles are handled by Tailwind CSS */
+/* Animation utilities for modal/popover transitions */
+.animate-in {
+ animation-duration: 200ms;
+ animation-fill-mode: forwards;
+ animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
+}
+
+.fade-in { animation-name: fadeIn; }
+.zoom-in-95 { animation-name: zoomIn95; }
+.slide-in-from-top-2 { animation-name: slideInFromTop2; }
+
+@keyframes fadeIn {
+ from { opacity: 0; }
+ to { opacity: 1; }
+}
+
+@keyframes zoomIn95 {
+ from { opacity: 0; transform: scale(0.95); }
+ to { opacity: 1; transform: scale(1); }
+}
+
+@keyframes slideInFromTop2 {
+ from { opacity: 0; transform: translateY(-0.5rem); }
+ to { opacity: 1; transform: translateY(0); }
+}
+
+/* Scoped scrollbar for containers */
+.custom-scrollbar::-webkit-scrollbar { width: 6px; height: 6px; }
+.custom-scrollbar::-webkit-scrollbar-track { background: transparent; }
+.custom-scrollbar::-webkit-scrollbar-thumb { background-color: #cbd5e1; border-radius: 20px; }
+.custom-scrollbar::-webkit-scrollbar-thumb:hover { background-color: #94a3b8; }
diff --git a/prisma/index.html b/prisma/index.html
index 518e559..639b954 100644
--- a/prisma/index.html
+++ b/prisma/index.html
@@ -7,67 +7,58 @@
-
+
+
No chat history yet.
+
+
) : (
sessions.map((session) => (
@@ -79,23 +82,33 @@ const Sidebar = ({
if (window.innerWidth < 1024) onClose();
}}
className={`
- group relative flex items-center gap-3 p-3 rounded-lg cursor-pointer transition-all
+ group relative flex items-center gap-3 p-3 rounded-xl cursor-pointer transition-all duration-200 border
${currentSessionId === session.id
- ? 'bg-white shadow-sm border border-slate-200 text-slate-900'
- : 'text-slate-600 hover:bg-slate-100 border border-transparent'}
+ ? 'bg-white shadow-sm border-slate-200 text-slate-900 z-10 ring-1 ring-slate-100'
+ : 'text-slate-600 hover:bg-white/60 hover:text-slate-900 border-transparent hover:border-slate-100 hover:shadow-sm'}
`}
>
-
+
+
+ No chat history yet
- onDeleteSession(session.id, e)}
- className="absolute right-2 top-1/2 -translate-y-1/2 p-1.5 rounded-md opacity-0 group-hover:opacity-100 hover:bg-red-50 hover:text-red-600 text-slate-400 transition-all"
+ className="absolute right-2 top-1/2 -translate-y-1/2 p-1.5 rounded-lg opacity-0 group-hover:opacity-100 hover:bg-red-50 hover:text-red-600 text-slate-300 transition-all scale-90 hover:scale-100 hover:shadow-sm"
title="Delete Chat"
>
diff --git a/prisma/components/settings/ThinkingSection.tsx b/prisma/components/settings/ThinkingSection.tsx
index b2513de..ca74398 100644
--- a/prisma/components/settings/ThinkingSection.tsx
+++ b/prisma/components/settings/ThinkingSection.tsx
@@ -1,6 +1,6 @@
import React from 'react';
-import { RefreshCw } from 'lucide-react';
+import { RefreshCw, Search } from 'lucide-react';
import { AppConfig, ModelOption } from '../../types';
import { getValidThinkingLevels } from '../../config';
import LevelSelect from './LevelSelect';
@@ -39,6 +39,25 @@ const ThinkingSection = ({ config, setConfig, model }: ThinkingSectionProps) =>
+ {session.title}
- - {new Date(session.createdAt).toLocaleDateString()} +
+
+
+
+
+
+ {session.title} +
+ + {new Date(session.createdAt).toLocaleDateString(undefined, { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' })}
+
+
+
+
+
+
+
+ Web Search
+Allow the model to use grounded web search when supported.
+