mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-11-13 08:51:28 +00:00
better regex for grounding, UI improvements
This commit is contained in:
parent
569466e446
commit
7a820068ba
4 changed files with 164 additions and 119 deletions
|
|
@ -49,6 +49,14 @@ export const RightPanel = () => {
|
|||
|
||||
const [chatInput, setChatInput] = useState('');
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Auto-scroll to bottom when messages update or while streaming
|
||||
useEffect(() => {
|
||||
if (messagesEndRef.current) {
|
||||
messagesEndRef.current.scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
}, [chatMessages, isChatLoading]);
|
||||
|
||||
const resolveFilePathForUI = useCallback((requestedPath: string): string | null => {
|
||||
const req = requestedPath.replace(/\\/g, '/').replace(/^\.?\//, '').toLowerCase();
|
||||
|
|
@ -383,17 +391,8 @@ export const RightPanel = () => {
|
|||
</a>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
className="text-accent underline underline-offset-2 hover:text-purple-300"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
// Non-citation link - just render as plain text (not a useless external link)
|
||||
return <span {...props}>{children}</span>;
|
||||
},
|
||||
code: ({ className, children, ...props }) => {
|
||||
const match = /language-(\w+)/.exec(className || '');
|
||||
|
|
@ -631,6 +630,8 @@ export const RightPanel = () => {
|
|||
|
||||
</div>
|
||||
)}
|
||||
{/* Scroll anchor for auto-scroll */}
|
||||
<div ref={messagesEndRef} />
|
||||
</div>
|
||||
|
||||
{/* Input */}
|
||||
|
|
|
|||
|
|
@ -47,22 +47,22 @@ import {
|
|||
export const BASE_SYSTEM_PROMPT = `You are Nexus, a Code Analysis Agent with access to a Knowledge Graph. Your responses MUST be grounded.
|
||||
|
||||
## ⚠️ MANDATORY: GROUNDING
|
||||
Every factual claim MUST include a citation: \`[[filename:line]]\`
|
||||
Every factual claim MUST include a citation in [[file:line]] format.
|
||||
- When you mention a function, class, or file → cite where you found it
|
||||
- When you describe behavior → cite the code that proves it
|
||||
- NO citation = NO claim. If you can't cite it, say "I didn't find evidence for this."
|
||||
|
||||
Examples:
|
||||
- "The \`AuthService\` handles login [[src/services/auth.ts:45]]."
|
||||
- "This function calls \`validateToken\` [[src/utils.ts:12]] which throws on invalid tokens [[src/utils.ts:28]]."
|
||||
- "The AuthService handles login [[src/services/auth.ts:45]]."
|
||||
- "This function calls validateToken [[src/utils.ts:12]] which throws on invalid tokens [[src/utils.ts:28]]."
|
||||
|
||||
## 🧠 CORE PROTOCOL
|
||||
You are an investigator. For each question:
|
||||
1. **Search** → Use \`search\` or \`grep\` to find relevant code
|
||||
2. **Read** → Use \`read\` to see the actual source
|
||||
3. **Trace** → Use \`cypher\` to follow connections in the graph
|
||||
4. **Cite** → Ground every finding with \`[[file:line]]\`
|
||||
5. **Highlight** → Visualize key nodes with \`highlight\`
|
||||
1. **Search** → Use search or grep to find relevant code
|
||||
2. **Read** → Use read to see the actual source
|
||||
3. **Trace** → Use cypher to follow connections in the graph
|
||||
4. **Cite** → Ground every finding with [[file:line]]
|
||||
5. **Highlight** → Visualize key nodes with highlight
|
||||
|
||||
## 🛠️ TOOLS
|
||||
- **\`search\`** — Hybrid search (keyword + semantic). Returns code matches with graph connections.
|
||||
|
|
@ -178,11 +178,6 @@ export const createGraphRAGAgent = (
|
|||
? buildDynamicSystemPrompt(BASE_SYSTEM_PROMPT, codebaseContext)
|
||||
: BASE_SYSTEM_PROMPT;
|
||||
|
||||
// Log the full prompt for debugging
|
||||
if (import.meta.env?.DEV) {
|
||||
console.log('📝 Full System Prompt:\n', systemPrompt);
|
||||
}
|
||||
|
||||
const agent = createReactAgent({
|
||||
llm: model as any,
|
||||
tools: tools as any,
|
||||
|
|
|
|||
|
|
@ -44,17 +44,17 @@ interface AppState {
|
|||
// View state
|
||||
viewMode: ViewMode;
|
||||
setViewMode: (mode: ViewMode) => void;
|
||||
|
||||
|
||||
// Graph data
|
||||
graph: KnowledgeGraph | null;
|
||||
setGraph: (graph: KnowledgeGraph | null) => void;
|
||||
fileContents: Map<string, string>;
|
||||
setFileContents: (contents: Map<string, string>) => void;
|
||||
|
||||
|
||||
// Selection
|
||||
selectedNode: GraphNode | null;
|
||||
setSelectedNode: (node: GraphNode | null) => void;
|
||||
|
||||
|
||||
// Right Panel (unified Code + Chat)
|
||||
isRightPanelOpen: boolean;
|
||||
setRightPanelOpen: (open: boolean) => void;
|
||||
|
|
@ -62,15 +62,15 @@ interface AppState {
|
|||
setRightPanelTab: (tab: RightPanelTab) => void;
|
||||
openCodePanel: () => void;
|
||||
openChatPanel: () => void;
|
||||
|
||||
|
||||
// Filters
|
||||
visibleLabels: NodeLabel[];
|
||||
toggleLabelVisibility: (label: NodeLabel) => void;
|
||||
|
||||
|
||||
// Depth filter (N hops from selection)
|
||||
depthFilter: number | null;
|
||||
setDepthFilter: (depth: number | null) => void;
|
||||
|
||||
|
||||
// Query state
|
||||
highlightedNodeIds: Set<string>;
|
||||
setHighlightedNodeIds: (ids: Set<string>) => void;
|
||||
|
|
@ -83,34 +83,34 @@ interface AppState {
|
|||
queryResult: QueryResult | null;
|
||||
setQueryResult: (result: QueryResult | null) => void;
|
||||
clearQueryHighlights: () => void;
|
||||
|
||||
|
||||
// Progress
|
||||
progress: PipelineProgress | null;
|
||||
setProgress: (progress: PipelineProgress | null) => void;
|
||||
|
||||
|
||||
// Project info
|
||||
projectName: string;
|
||||
setProjectName: (name: string) => void;
|
||||
|
||||
|
||||
// Worker API (shared across app)
|
||||
runPipeline: (file: File, onProgress: (p: PipelineProgress) => void) => Promise<PipelineResult>;
|
||||
runPipelineFromFiles: (files: FileEntry[], onProgress: (p: PipelineProgress) => void) => Promise<PipelineResult>;
|
||||
runQuery: (cypher: string) => Promise<any[]>;
|
||||
isDatabaseReady: () => Promise<boolean>;
|
||||
|
||||
|
||||
// Embedding state
|
||||
embeddingStatus: EmbeddingStatus;
|
||||
embeddingProgress: EmbeddingProgress | null;
|
||||
|
||||
|
||||
// Embedding methods
|
||||
startEmbeddings: (forceDevice?: 'webgpu' | 'wasm') => Promise<void>;
|
||||
semanticSearch: (query: string, k?: number) => Promise<SemanticSearchResult[]>;
|
||||
semanticSearchWithContext: (query: string, k?: number, hops?: number) => Promise<any[]>;
|
||||
isEmbeddingReady: boolean;
|
||||
|
||||
|
||||
// Debug/test methods
|
||||
testArrayParams: () => Promise<{ success: boolean; error?: string }>;
|
||||
|
||||
|
||||
// LLM/Agent state
|
||||
llmSettings: LLMSettings;
|
||||
isSettingsPanelOpen: boolean;
|
||||
|
|
@ -118,18 +118,18 @@ interface AppState {
|
|||
isAgentReady: boolean;
|
||||
isAgentInitializing: boolean;
|
||||
agentError: string | null;
|
||||
|
||||
|
||||
// Chat state
|
||||
chatMessages: ChatMessage[];
|
||||
isChatLoading: boolean;
|
||||
currentToolCalls: ToolCallInfo[];
|
||||
|
||||
|
||||
// LLM methods
|
||||
refreshLLMSettings: () => void;
|
||||
initializeAgent: () => Promise<void>;
|
||||
sendChatMessage: (message: string) => Promise<void>;
|
||||
clearChat: () => void;
|
||||
|
||||
|
||||
// Code References Panel
|
||||
codeReferences: CodeReference[];
|
||||
isCodePanelOpen: boolean;
|
||||
|
|
@ -146,36 +146,36 @@ const AppStateContext = createContext<AppState | null>(null);
|
|||
export const AppStateProvider = ({ children }: { children: ReactNode }) => {
|
||||
// View state
|
||||
const [viewMode, setViewMode] = useState<ViewMode>('onboarding');
|
||||
|
||||
|
||||
// Graph data
|
||||
const [graph, setGraph] = useState<KnowledgeGraph | null>(null);
|
||||
const [fileContents, setFileContents] = useState<Map<string, string>>(new Map());
|
||||
|
||||
|
||||
// Selection
|
||||
const [selectedNode, setSelectedNode] = useState<GraphNode | null>(null);
|
||||
|
||||
|
||||
// Right Panel
|
||||
const [isRightPanelOpen, setRightPanelOpen] = useState(false);
|
||||
const [rightPanelTab, setRightPanelTab] = useState<RightPanelTab>('code');
|
||||
|
||||
|
||||
const openCodePanel = useCallback(() => {
|
||||
// Legacy API: used by graph/tree selection.
|
||||
// Code is now shown in the Code References Panel (left of the graph),
|
||||
// so "openCodePanel" just ensures that panel becomes visible when needed.
|
||||
setCodePanelOpen(true);
|
||||
}, []);
|
||||
|
||||
|
||||
const openChatPanel = useCallback(() => {
|
||||
setRightPanelOpen(true);
|
||||
setRightPanelTab('chat');
|
||||
}, []);
|
||||
|
||||
|
||||
// Filters
|
||||
const [visibleLabels, setVisibleLabels] = useState<NodeLabel[]>(DEFAULT_VISIBLE_LABELS);
|
||||
|
||||
|
||||
// Depth filter
|
||||
const [depthFilter, setDepthFilter] = useState<number | null>(null);
|
||||
|
||||
|
||||
// Query state
|
||||
const [highlightedNodeIds, setHighlightedNodeIds] = useState<Set<string>>(new Set());
|
||||
const [queryResult, setQueryResult] = useState<QueryResult | null>(null);
|
||||
|
|
@ -192,34 +192,34 @@ export const AppStateProvider = ({ children }: { children: ReactNode }) => {
|
|||
const clearAIToolHighlights = useCallback(() => {
|
||||
setAIToolHighlightedNodeIds(new Set());
|
||||
}, []);
|
||||
|
||||
|
||||
const clearQueryHighlights = useCallback(() => {
|
||||
setHighlightedNodeIds(new Set());
|
||||
setQueryResult(null);
|
||||
}, []);
|
||||
|
||||
|
||||
// Progress
|
||||
const [progress, setProgress] = useState<PipelineProgress | null>(null);
|
||||
|
||||
|
||||
// Project info
|
||||
const [projectName, setProjectName] = useState<string>('');
|
||||
|
||||
|
||||
// Embedding state
|
||||
const [embeddingStatus, setEmbeddingStatus] = useState<EmbeddingStatus>('idle');
|
||||
const [embeddingProgress, setEmbeddingProgress] = useState<EmbeddingProgress | null>(null);
|
||||
|
||||
|
||||
// LLM/Agent state
|
||||
const [llmSettings, setLLMSettings] = useState<LLMSettings>(loadSettings);
|
||||
const [isSettingsPanelOpen, setSettingsPanelOpen] = useState(false);
|
||||
const [isAgentReady, setIsAgentReady] = useState(false);
|
||||
const [isAgentInitializing, setIsAgentInitializing] = useState(false);
|
||||
const [agentError, setAgentError] = useState<string | null>(null);
|
||||
|
||||
|
||||
// Chat state
|
||||
const [chatMessages, setChatMessages] = useState<ChatMessage[]>([]);
|
||||
const [isChatLoading, setIsChatLoading] = useState(false);
|
||||
const [currentToolCalls, setCurrentToolCalls] = useState<ToolCallInfo[]>([]);
|
||||
|
||||
|
||||
// Code References Panel state
|
||||
const [codeReferences, setCodeReferences] = useState<CodeReference[]>([]);
|
||||
const [isCodePanelOpen, setCodePanelOpen] = useState(false);
|
||||
|
|
@ -278,18 +278,18 @@ export const AppStateProvider = ({ children }: { children: ReactNode }) => {
|
|||
const addCodeReference = useCallback((ref: Omit<CodeReference, 'id'>) => {
|
||||
const id = `ref-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
||||
const newRef: CodeReference = { ...ref, id };
|
||||
|
||||
|
||||
setCodeReferences(prev => {
|
||||
// Don't add duplicates (same file + line range)
|
||||
const isDuplicate = prev.some(r =>
|
||||
r.filePath === ref.filePath &&
|
||||
r.startLine === ref.startLine &&
|
||||
const isDuplicate = prev.some(r =>
|
||||
r.filePath === ref.filePath &&
|
||||
r.startLine === ref.startLine &&
|
||||
r.endLine === ref.endLine
|
||||
);
|
||||
if (isDuplicate) return prev;
|
||||
return [...prev, newRef];
|
||||
});
|
||||
|
||||
|
||||
// Auto-open panel when references are added
|
||||
setCodePanelOpen(true);
|
||||
|
||||
|
|
@ -302,7 +302,7 @@ export const AppStateProvider = ({ children }: { children: ReactNode }) => {
|
|||
endLine: ref.endLine,
|
||||
ts: Date.now(),
|
||||
});
|
||||
|
||||
|
||||
// Track AI highlights separately so they can be toggled off in the UI
|
||||
if (ref.nodeId && ref.source === 'ai') {
|
||||
setAICitationHighlightedNodeIds(prev => new Set([...prev, ref.nodeId!]));
|
||||
|
|
@ -367,7 +367,7 @@ export const AppStateProvider = ({ children }: { children: ReactNode }) => {
|
|||
): Promise<PipelineResult> => {
|
||||
const api = apiRef.current;
|
||||
if (!api) throw new Error('Worker not initialized');
|
||||
|
||||
|
||||
const proxiedOnProgress = Comlink.proxy(onProgress);
|
||||
const serializedResult = await api.runPipeline(file, proxiedOnProgress);
|
||||
return deserializePipelineResult(serializedResult, createKnowledgeGraph);
|
||||
|
|
@ -379,7 +379,7 @@ export const AppStateProvider = ({ children }: { children: ReactNode }) => {
|
|||
): Promise<PipelineResult> => {
|
||||
const api = apiRef.current;
|
||||
if (!api) throw new Error('Worker not initialized');
|
||||
|
||||
|
||||
const proxiedOnProgress = Comlink.proxy(onProgress);
|
||||
const serializedResult = await api.runPipelineFromFiles(files, proxiedOnProgress);
|
||||
return deserializePipelineResult(serializedResult, createKnowledgeGraph);
|
||||
|
|
@ -405,14 +405,14 @@ export const AppStateProvider = ({ children }: { children: ReactNode }) => {
|
|||
const startEmbeddings = useCallback(async (forceDevice?: 'webgpu' | 'wasm'): Promise<void> => {
|
||||
const api = apiRef.current;
|
||||
if (!api) throw new Error('Worker not initialized');
|
||||
|
||||
|
||||
setEmbeddingStatus('loading');
|
||||
setEmbeddingProgress(null);
|
||||
|
||||
|
||||
try {
|
||||
const proxiedOnProgress = Comlink.proxy((progress: EmbeddingProgress) => {
|
||||
setEmbeddingProgress(progress);
|
||||
|
||||
|
||||
// Update status based on phase
|
||||
switch (progress.phase) {
|
||||
case 'loading-model':
|
||||
|
|
@ -432,12 +432,12 @@ export const AppStateProvider = ({ children }: { children: ReactNode }) => {
|
|||
break;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
await api.startEmbeddingPipeline(proxiedOnProgress, forceDevice);
|
||||
} catch (error: any) {
|
||||
// Check if it's WebGPU not available - let caller handle the dialog
|
||||
if (error?.name === 'WebGPUNotAvailableError' ||
|
||||
error?.message?.includes('WebGPU not available')) {
|
||||
if (error?.name === 'WebGPUNotAvailableError' ||
|
||||
error?.message?.includes('WebGPU not available')) {
|
||||
setEmbeddingStatus('idle'); // Reset to idle so user can try again
|
||||
} else {
|
||||
setEmbeddingStatus('error');
|
||||
|
|
@ -581,7 +581,7 @@ export const AppStateProvider = ({ children }: { children: ReactNode }) => {
|
|||
.map(s => s.content)
|
||||
.filter(Boolean);
|
||||
const content = contentParts.join('\n\n');
|
||||
|
||||
|
||||
setChatMessages(prev => {
|
||||
const existing = prev.find(m => m.id === assistantMessageId);
|
||||
const newMessage: ChatMessage = {
|
||||
|
|
@ -717,17 +717,17 @@ export const AppStateProvider = ({ children }: { children: ReactNode }) => {
|
|||
idx = toolCallsForMessage.findIndex(t => t.name === tc.name && !t.result);
|
||||
}
|
||||
if (idx >= 0) {
|
||||
toolCallsForMessage[idx] = {
|
||||
...toolCallsForMessage[idx],
|
||||
result: tc.result,
|
||||
status: 'completed'
|
||||
toolCallsForMessage[idx] = {
|
||||
...toolCallsForMessage[idx],
|
||||
result: tc.result,
|
||||
status: 'completed'
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// Also update the tool call in steps
|
||||
const stepIdx = stepsForMessage.findIndex(s =>
|
||||
const stepIdx = stepsForMessage.findIndex(s =>
|
||||
s.type === 'tool_call' && s.toolCall && (
|
||||
s.toolCall.id === tc.id ||
|
||||
s.toolCall.id === tc.id ||
|
||||
(s.toolCall.name === tc.name && s.toolCall.status === 'running')
|
||||
)
|
||||
);
|
||||
|
|
@ -741,7 +741,7 @@ export const AppStateProvider = ({ children }: { children: ReactNode }) => {
|
|||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// Update currentToolCalls
|
||||
setCurrentToolCalls(prev => {
|
||||
let targetIdx = prev.findIndex(t => t.id === tc.id);
|
||||
|
|
@ -752,16 +752,16 @@ export const AppStateProvider = ({ children }: { children: ReactNode }) => {
|
|||
targetIdx = prev.findIndex(t => t.name === tc.name && !t.result);
|
||||
}
|
||||
if (targetIdx >= 0) {
|
||||
return prev.map((t, i) => i === targetIdx
|
||||
? { ...t, result: tc.result, status: 'completed' }
|
||||
return prev.map((t, i) => i === targetIdx
|
||||
? { ...t, result: tc.result, status: 'completed' }
|
||||
: t
|
||||
);
|
||||
}
|
||||
return prev;
|
||||
});
|
||||
|
||||
|
||||
updateMessage();
|
||||
|
||||
|
||||
// Parse highlight marker from tool results
|
||||
if (tc.result) {
|
||||
const highlightMatch = tc.result.match(/\[HIGHLIGHT_NODES:([^\]]+)\]/);
|
||||
|
|
@ -770,12 +770,12 @@ export const AppStateProvider = ({ children }: { children: ReactNode }) => {
|
|||
if (rawIds.length > 0 && graph) {
|
||||
const matchedIds = new Set<string>();
|
||||
const graphNodeIds = graph.nodes.map(n => n.id);
|
||||
|
||||
|
||||
for (const rawId of rawIds) {
|
||||
if (graphNodeIds.includes(rawId)) {
|
||||
matchedIds.add(rawId);
|
||||
} else {
|
||||
const found = graphNodeIds.find(gid =>
|
||||
const found = graphNodeIds.find(gid =>
|
||||
gid.endsWith(rawId) || gid.endsWith(':' + rawId)
|
||||
);
|
||||
if (found) {
|
||||
|
|
@ -783,7 +783,7 @@ export const AppStateProvider = ({ children }: { children: ReactNode }) => {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (matchedIds.size > 0) {
|
||||
setAIToolHighlightedNodeIds(matchedIds);
|
||||
}
|
||||
|
|
@ -826,7 +826,7 @@ export const AppStateProvider = ({ children }: { children: ReactNode }) => {
|
|||
setCodeReferences(prev => {
|
||||
const ref = prev.find(r => r.id === id);
|
||||
const newRefs = prev.filter(r => r.id !== id);
|
||||
|
||||
|
||||
// Remove AI citation highlight if this was the only AI reference to that node
|
||||
if (ref?.nodeId && ref.source === 'ai') {
|
||||
const stillReferenced = newRefs.some(r => r.nodeId === ref.nodeId && r.source === 'ai');
|
||||
|
|
@ -838,12 +838,12 @@ export const AppStateProvider = ({ children }: { children: ReactNode }) => {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Auto-close panel if no references left AND no selection in top viewer
|
||||
if (newRefs.length === 0 && !selectedNode) {
|
||||
setCodePanelOpen(false);
|
||||
}
|
||||
|
||||
|
||||
return newRefs;
|
||||
});
|
||||
}, [selectedNode]);
|
||||
|
|
|
|||
111
src/index.css
111
src/index.css
|
|
@ -10,20 +10,20 @@
|
|||
--color-surface: #101018;
|
||||
--color-elevated: #16161f;
|
||||
--color-hover: #1c1c28;
|
||||
|
||||
|
||||
/* Borders */
|
||||
--color-border-subtle: #1e1e2a;
|
||||
--color-border-default: #2a2a3a;
|
||||
|
||||
|
||||
/* Text */
|
||||
--color-text-primary: #e4e4ed;
|
||||
--color-text-secondary: #8888a0;
|
||||
--color-text-muted: #5a5a70;
|
||||
|
||||
|
||||
/* Accent */
|
||||
--color-accent: #7c3aed;
|
||||
--color-accent-dim: #5b21b6;
|
||||
|
||||
|
||||
/* Node colors */
|
||||
--color-node-file: #3b82f6;
|
||||
--color-node-folder: #6366f1;
|
||||
|
|
@ -51,40 +51,65 @@
|
|||
|
||||
/* Keyframes */
|
||||
@keyframes breathe {
|
||||
0%, 100% {
|
||||
|
||||
0%,
|
||||
100% {
|
||||
border-color: #2a2a3a;
|
||||
box-shadow: 0 0 0 0 rgba(124, 58, 237, 0.3);
|
||||
}
|
||||
50% {
|
||||
|
||||
50% {
|
||||
border-color: #7c3aed;
|
||||
box-shadow: 0 0 40px 10px rgba(124, 58, 237, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse-glow {
|
||||
0%, 100% {
|
||||
|
||||
0%,
|
||||
100% {
|
||||
transform: scale(1);
|
||||
box-shadow: 0 0 40px rgba(124, 58, 237, 0.4);
|
||||
}
|
||||
50% {
|
||||
|
||||
50% {
|
||||
transform: scale(1.1);
|
||||
box-shadow: 0 0 80px rgba(124, 58, 237, 0.6);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slide-in {
|
||||
from { opacity: 0; transform: translateX(20px); }
|
||||
to { opacity: 1; transform: translateX(0); }
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(20px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slide-up {
|
||||
from { opacity: 0; transform: translateY(20px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fade-in {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════
|
||||
|
|
@ -94,7 +119,9 @@
|
|||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html, body, #root {
|
||||
html,
|
||||
body,
|
||||
#root {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
|
@ -182,15 +209,29 @@ body {
|
|||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.chat-prose h1 { font-size: 1.25em; }
|
||||
.chat-prose h2 { font-size: 1.125em; }
|
||||
.chat-prose h3 { font-size: 1em; }
|
||||
.chat-prose h4 { font-size: 0.875em; text-transform: uppercase; letter-spacing: 0.05em; color: var(--color-text-secondary); }
|
||||
.chat-prose h1 {
|
||||
font-size: 1.25em;
|
||||
}
|
||||
|
||||
.chat-prose > h1:first-child,
|
||||
.chat-prose > h2:first-child,
|
||||
.chat-prose > h3:first-child,
|
||||
.chat-prose > h4:first-child {
|
||||
.chat-prose h2 {
|
||||
font-size: 1.125em;
|
||||
}
|
||||
|
||||
.chat-prose h3 {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.chat-prose h4 {
|
||||
font-size: 0.875em;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.chat-prose>h1:first-child,
|
||||
.chat-prose>h2:first-child,
|
||||
.chat-prose>h3:first-child,
|
||||
.chat-prose>h4:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
|
|
@ -217,15 +258,23 @@ body {
|
|||
margin: 0.25em 0;
|
||||
}
|
||||
|
||||
/* Inline code */
|
||||
/* Inline code - VS Code style */
|
||||
.chat-prose code:not([class*="language-"]) {
|
||||
padding: 0.2em 0.4em;
|
||||
background: var(--color-surface);
|
||||
border-radius: 4px;
|
||||
padding: 0.2em 0.5em;
|
||||
background: rgba(110, 118, 129, 0.2);
|
||||
border-radius: 6px;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.85em;
|
||||
color: var(--color-accent);
|
||||
border: 1px solid var(--color-border-subtle);
|
||||
font-size: 0.875em;
|
||||
color: #e6b450 !important;
|
||||
border: 1px solid rgba(110, 118, 129, 0.3);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Ensure inline code keeps its color inside links and other elements */
|
||||
.chat-prose a code:not([class*="language-"]),
|
||||
.chat-prose strong code:not([class*="language-"]),
|
||||
.chat-prose em code:not([class*="language-"]) {
|
||||
color: #e6b450 !important;
|
||||
}
|
||||
|
||||
/* Code blocks */
|
||||
|
|
@ -302,4 +351,4 @@ body {
|
|||
|
||||
.sigma-container canvas {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue