feat: add Files tab for browsing workspace files (#154)
* feat: add Files tab for browsing workspace files - Add Files tab between Diff and Terminal in header - Implement hierarchical file tree with expand/collapse - Add fuzzy search with debouncing and relevance ranking - Support gitignore filtering via `git check-ignore` (web + desktop) - Add syntax highlighting for 150+ file types - Add image preview (SVG, PNG, JPG, etc.) - Add line numbers, wrap toggle, and copy button - Desktop: split-pane layout matching DiffView - Mobile: drill-in navigation with full-width sidebar - Update help dialog with Cmd+3 shortcut - Increase header breakpoint to 940px for new tab * feat: enhance context and session stores to track agent/model/variant choices for historical sessions * feat: implement line selection and commenting functionality in FilesView
This commit is contained in:
committed by
GitHub
parent
ecf81c901d
commit
1be5dfda05
@@ -265,21 +265,37 @@ export const useContextStore = create<ContextStore>()(
|
||||
const allMessages = sessionMessages.filter((m: any) => m.info.role === "assistant" || m.info.role === "user").sort((a: any, b: any) => a.info.time.created - b.info.time.created);
|
||||
const assistantMessages = sessionMessages.filter((m: any) => m.info.role === "assistant").sort((a: any, b: any) => a.info.time.created - b.info.time.created);
|
||||
|
||||
// Track variant from user messages to apply to corresponding assistant response
|
||||
let pendingVariant: string | undefined = undefined;
|
||||
let pendingUserModel: { providerID: string; modelID: string } | undefined = undefined;
|
||||
|
||||
for (let messageIndex = 0; messageIndex < allMessages.length; messageIndex++) {
|
||||
const message = allMessages[messageIndex];
|
||||
const { info } = message;
|
||||
const infoAny = info as any;
|
||||
|
||||
// User messages have variant and model info in different structure
|
||||
if (infoAny.role === "user") {
|
||||
// User message: variant is top-level, model is nested in model.providerID/modelID
|
||||
pendingVariant = typeof infoAny.variant === 'string' && infoAny.variant.trim().length > 0
|
||||
? infoAny.variant
|
||||
: undefined;
|
||||
pendingUserModel = infoAny.model?.providerID && infoAny.model?.modelID
|
||||
? { providerID: infoAny.model.providerID, modelID: infoAny.model.modelID }
|
||||
: undefined;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Assistant message: providerID/modelID are top-level
|
||||
if (infoAny.providerID && infoAny.modelID) {
|
||||
const agentName = extractAgentFromMessage(infoAny, assistantMessages.indexOf(message));
|
||||
|
||||
if (agentName && agents.find((a) => a.name === agentName)) {
|
||||
const resolvedVariant = typeof infoAny.variant === 'string' && infoAny.variant.trim().length > 0
|
||||
? infoAny.variant
|
||||
: undefined;
|
||||
|
||||
if (resolvedVariant) {
|
||||
saveAgentModelVariantForSession(sessionId, agentName, infoAny.providerID, infoAny.modelID, resolvedVariant);
|
||||
// Apply pending variant from user message if model matches
|
||||
if (pendingVariant && pendingUserModel &&
|
||||
pendingUserModel.providerID === infoAny.providerID &&
|
||||
pendingUserModel.modelID === infoAny.modelID) {
|
||||
saveAgentModelVariantForSession(sessionId, agentName, infoAny.providerID, infoAny.modelID, pendingVariant);
|
||||
}
|
||||
|
||||
const choice = {
|
||||
@@ -294,6 +310,10 @@ export const useContextStore = create<ContextStore>()(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clear pending variant after processing assistant message
|
||||
pendingVariant = undefined;
|
||||
pendingUserModel = undefined;
|
||||
}
|
||||
|
||||
for (const [agentName, choice] of agentLastChoices) {
|
||||
|
||||
@@ -288,6 +288,24 @@ export const useSessionStore = create<SessionStore>()(
|
||||
}
|
||||
|
||||
get().trimToViewportWindow(id, ACTIVE_SESSION_WINDOW);
|
||||
|
||||
// Analyze session messages to extract agent/model/variant choices
|
||||
// This ensures context is available even when ModelControls isn't mounted
|
||||
const sessionMessages = get().messages.get(id);
|
||||
if (sessionMessages && sessionMessages.length > 0) {
|
||||
const agents = useConfigStore.getState().agents;
|
||||
if (agents.length > 0) {
|
||||
try {
|
||||
await useContextStore.getState().analyzeAndSaveExternalSessionChoices(
|
||||
id,
|
||||
agents,
|
||||
get().messages
|
||||
);
|
||||
} catch (error) {
|
||||
console.warn('Failed to analyze session choices:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get().evictLeastRecentlyUsed();
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { SidebarSection } from '@/constants/sidebar';
|
||||
import { getSafeStorage } from './utils/safeStorage';
|
||||
import { SEMANTIC_TYPOGRAPHY, getTypographyVariable, type SemanticTypographyKey } from '@/lib/typography';
|
||||
|
||||
export type MainTab = 'chat' | 'git' | 'diff' | 'terminal';
|
||||
export type MainTab = 'chat' | 'git' | 'diff' | 'terminal' | 'files';
|
||||
export type EventStreamStatus =
|
||||
| 'idle'
|
||||
| 'connecting'
|
||||
|
||||
Reference in New Issue
Block a user