Skip to content

Commit d735ceb

Browse files
committed
Refactor ChatPage and SettingPage components for improved layout and functionality
- Updated ChatPage to use a single-column layout with a minimalist header. - Replaced button elements with a consistent Button component for navigation. - Adjusted navigation routes to point to '/chat/list' and added a new chat route. - Enhanced MessagesArea component to handle streaming responses correctly. - Simplified input area with suggestion chips and improved styling. - Refactored SettingPage to streamline tab navigation and component rendering. - Removed unused imports and commented-out code for cleaner structure. - Updated router configuration to reflect new routes and removed deprecated ones.
1 parent c78196e commit d735ceb

22 files changed

+1665
-2124
lines changed

memory-bank/active_context.md

+4-20
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,9 @@
11
# Active Context
22

3-
## Current Focus
4-
**Assistant Architecture - Phase 2 (Backend/Stores/UI Implementation):** Verify Assistant CRUD functionality and proceed with full UI implementation and backend integration.
3+
*This file is automatically managed by the AI Council.*
54

6-
## Next Steps
7-
1. **Testing:** Verify Assistant CRUD operations (Create, List, Update, Delete) are working correctly via the UI and backend logs.
8-
2. **Frontend:** Implement the full UI functionality within `AssistantSettings.tsx` (forms, validation, interaction with stores for CRUD).
9-
3. **Frontend:** Replace placeholder Assistant data/logic in `InputArea.tsx` and `DefaultAssistantSettings.tsx` with integration with the new `assistantStores`.
10-
4. **Refactor:** Update `ConfigResolver` and `AiStreamer` to use the real `AssistantManager` to fetch Assistant details and apply their configuration (model, instructions).
11-
5. **Refactor:** Update `ChatSessionManager` to handle `assistantId` in `ChatConfig`.
5+
**Current Task:** None.
126

13-
## Recent Changes
14-
* Completed systematic UI/UX refactoring based on user principles (minimalism, theme, borderless, etc.). [Resolved]
15-
* Fixed UI bugs: model selector text, image previews, Assistant settings dropdown cutoff, `CustomSelect` mouse selection. [Resolved]
16-
* Enhanced `ChatListPage`: Improved delete visibility, added bulk delete UI, added resource placeholder. [Resolved]
17-
* Pivoted to Assistant architecture: Updated types, refactored relevant frontend components (`InputArea`, `ChatPage`, `SettingPage`, `DefaultAssistantSettings`), created placeholder `AssistantSettings` UI, fixed backend type errors (`configResolver`, `SetDefaultConfigHandler`). [Phase 1 Done]
18-
* Removed redundant "Default Assistant" card from General Settings. [Done]
19-
* Implemented backend handlers (`Get`, `Create`, `Update`, `Delete`) for Assistant management. [Done]
20-
* Debugged and resolved "No handler found" errors and `assistants/list` timeout by adding logging and removing debounce from `AssistantManager.saveAssistants`. [Done]
7+
**Next Action:** Awaiting new user directive.
218

22-
## Active Decisions
23-
* Proceeding with Assistant-based architecture.
24-
* Prioritize backend/store implementation for Assistants next.
25-
* File rename `DefaultModelSettings.tsx` -> `DefaultAssistantSettings.tsx` still pending (manual user action).
9+
**Waiting For:** User Input.

memory-bank/progress.md

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
* **[Resolved] Assistant Settings UI Bugs:** Fixed dropdown cutoff issue in modal, resolved mouse click selection issue in `CustomSelect`.
4646
* **[Resolved] Assistant CRUD Backend:** Implemented and registered backend handlers (`Get`, `Create`, `Update`, `Delete`) for Assistant management. Debugged "No handler found" error (related to build/cache).
4747
* **[Resolved] Assistant List Timeout:** Removed debounce timer from `AssistantManager.saveAssistants` to fix timeout during initial load.
48+
* **[Resolved] UI Refactoring (Phase 2 - Expandable Edit):** Implemented expandable inline editing for Assistants and Providers settings based on user feedback. Adjusted main app layout (`app.tsx`) to use header navigation instead of a sidebar. Added search to Assistants and Providers lists.
4849

4950
## What's Left / Known Issues
5051
* **Assistant Architecture (Phase 2+):**

src/extension.ts

+2
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ import { GetAssistantsHandler } from './webview/handlers/GetAssistantsHandler';
6767
import { CreateAssistantHandler } from './webview/handlers/CreateAssistantHandler'; // Import CreateAssistantHandler
6868
import { UpdateAssistantHandler } from './webview/handlers/UpdateAssistantHandler'; // Import UpdateAssistantHandler
6969
import { DeleteAssistantHandler } from './webview/handlers/DeleteAssistantHandler'; // Import DeleteAssistantHandler
70+
import { CreateNewChatHandler } from './webview/handlers/CreateNewChatHandler'; // Re-import CreateNewChatHandler
7071
// Removed GetVertexProjectsHandler and GetVertexLocationsHandler imports
7172

7273
let aiServiceInstance: AiService | undefined = undefined;
@@ -204,6 +205,7 @@ class ZenCoderChatViewProvider implements vscode.WebviewViewProvider {
204205
new CreateAssistantHandler(), // Register CreateAssistantHandler
205206
new UpdateAssistantHandler(), // Register UpdateAssistantHandler
206207
new DeleteAssistantHandler(), // Register DeleteAssistantHandler
208+
new CreateNewChatHandler(), // Re-register CreateNewChatHandler
207209
// Removed GetVertexProjectsHandler and GetVertexLocationsHandler instances
208210
];
209211

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import * as vscode from 'vscode';
2+
import { RequestHandler, HandlerContext } from './RequestHandler';
3+
import { ChatSession } from '../../common/types';
4+
5+
// No payload needed for this request
6+
type CreateNewChatPayload = undefined;
7+
8+
// Define the expected return type (the newly created session)
9+
type CreateNewChatResult = ChatSession;
10+
11+
export class CreateNewChatHandler implements RequestHandler {
12+
public readonly requestType = 'createNewChat';
13+
14+
public async handle(payload: CreateNewChatPayload, context: HandlerContext): Promise<CreateNewChatResult> {
15+
console.log('[CreateNewChatHandler] Handling createNewChat request.');
16+
17+
try {
18+
// createChatSession should handle ID generation internally if no ID is passed
19+
const newSession = await context.chatSessionManager.createChatSession();
20+
if (!newSession) {
21+
throw new Error('Failed to create new chat session in manager.');
22+
}
23+
console.log(`[CreateNewChatHandler] Successfully created new session ${newSession.id}.`);
24+
25+
// No need to push updates here, the frontend will navigate and load the new session
26+
27+
return newSession; // Return the full session object
28+
} catch (error: any) {
29+
console.error(`[CreateNewChatHandler] Error creating new chat:`, error);
30+
vscode.window.showErrorMessage(`Failed to create new chat: ${error.message}`);
31+
throw new Error(`Failed to create new chat: ${error.message}`);
32+
}
33+
}
34+
}

src/webview/handlers/UpdateChatConfigHandler.ts

+34-11
Original file line numberDiff line numberDiff line change
@@ -27,20 +27,43 @@ export class UpdateChatConfigHandler implements RequestHandler {
2727
console.log(`[UpdateChatConfigHandler] Handling updateChatConfig for chat ID: ${chatId}`, config);
2828

2929
try {
30-
const chat = context.chatSessionManager.getChatSession(chatId); // Use chatSessionManager
30+
let chat = context.chatSessionManager.getChatSession(chatId); // Use chatSessionManager
31+
let mergedConfig: ChatConfig;
32+
3133
if (!chat) {
32-
throw new Error(`Chat session not found: ${chatId}`);
34+
// Session doesn't exist, likely a new chat. Create it first.
35+
console.log(`[UpdateChatConfigHandler] Chat session ${chatId} not found. Creating new session.`);
36+
chat = await context.chatSessionManager.createChatSession(chatId); // Create with default config
37+
if (!chat) {
38+
throw new Error(`Failed to create new chat session: ${chatId}`);
39+
}
40+
console.log(`[UpdateChatConfigHandler] Successfully created new session ${chatId}. Now applying config.`);
41+
// Now apply the desired config as an update
42+
mergedConfig = {
43+
...chat.config, // Start with the default config from the newly created chat
44+
...config // Apply partial updates from payload
45+
};
46+
await context.chatSessionManager.updateChatSession(chatId, { config: mergedConfig });
47+
console.log(`[UpdateChatConfigHandler] Successfully applied initial config to new session ${chatId}.`);
48+
// Re-fetch the chat to ensure we have the updated config for the return value
49+
chat = context.chatSessionManager.getChatSession(chatId);
50+
if (!chat) {
51+
// This should be highly unlikely if creation and update succeeded
52+
throw new Error(`Failed to retrieve newly created session ${chatId} after config update.`);
53+
}
54+
55+
} else {
56+
// Session exists, merge existing config with the partial update
57+
mergedConfig = {
58+
...chat.config, // Start with existing config
59+
...config // Apply partial updates
60+
};
61+
// Pass the complete, merged config object
62+
await context.chatSessionManager.updateChatSession(chatId, { config: mergedConfig }); // Use chatSessionManager
63+
console.log(`[UpdateChatConfigHandler] Successfully updated config for chat ${chatId}.`);
3364
}
34-
// Merge existing config with the partial update
35-
const mergedConfig: ChatConfig = {
36-
...chat.config, // Start with existing config
37-
...config // Apply partial updates
38-
};
39-
// Pass the complete, merged config object
40-
await context.chatSessionManager.updateChatSession(chatId, { config: mergedConfig }); // Use chatSessionManager
41-
console.log(`[UpdateChatConfigHandler] Successfully updated config for chat ${chatId}.`);
4265

43-
// Get the updated session data
66+
// Get the updated/created session data
4467
const updatedSession = context.chatSessionManager.getChatSession(chatId); // Use chatSessionManager
4568
if (updatedSession) {
4669
// Trigger a push update for the specific chat session

webview-ui/src/app.tsx

+83-31
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,103 @@
11
import { useStore } from '@nanostores/preact';
2-
import { ComponentChild } from 'preact';
3-
// Removed useEffect, useRef
4-
// Removed import './app.css';
2+
import { ComponentChild, VNode } from 'preact'; // Import VNode
3+
import { FunctionalComponent } from 'preact';
4+
import { useEffect, useRef } from 'preact/hooks'; // Added useRef
55
import { SettingPage } from './pages/SettingPage';
66
import { ChatPage } from './pages/ChatPage';
7-
import { ChatListPage } from './pages/ChatListPage'; // Import ChatListPage
8-
import { router } from './stores/router';
7+
import { ChatListPage } from './pages/ChatListPage';
8+
import { router, $location } from './stores/router'; // Import router and $location
9+
import { requestData } from './utils/communication';
10+
import { ChatSession } from '../../src/common/types';
11+
12+
// Removed HeaderNavItem component as header is removed
913

1014
export function App() {
1115
const page = useStore(router);
16+
const lastLocation = useStore($location); // Get last location state
17+
const initialRedirectDone = useRef(false); // Prevent multiple redirects
1218

13-
// Removed initial redirect logic
19+
// Effect to handle initial redirection from '/'
20+
useEffect(() => {
21+
// Only run once on initial load when page might be null or '/'
22+
if (!initialRedirectDone.current && (page === null || window.location.pathname === '/')) {
23+
initialRedirectDone.current = true; // Mark as done
24+
// Wait for lastLocation to be loaded (not 'loading' or 'error')
25+
if (typeof lastLocation === 'string' && lastLocation !== 'loading' && lastLocation !== 'error' && lastLocation !== '/') {
26+
console.log(`[App] Initial load: Redirecting to last location: ${lastLocation}`);
27+
router.open(lastLocation, true); // Redirect to last known location
28+
} else if (lastLocation !== 'loading') { // If loaded but invalid/error/root, go to new chat
29+
console.log(`[App] Initial load: No valid last location found (or was '/'). Redirecting to /chat/new.`);
30+
router.open('/chat/new', true); // Default redirect to new chat
31+
}
32+
// If lastLocation is still 'loading', the $location subscribe effect in router.ts will handle the initial open later.
33+
}
34+
}, [page, lastLocation]); // Rerun if page or lastLocation changes
1435

15-
// --- Page Routing ---
16-
let CurrentPage: ComponentChild = null;
36+
// Effect to handle /chat/new route
37+
useEffect(() => {
38+
if (page?.route === 'newChat') {
39+
console.log('[App] Detected /chat/new route, requesting new session...');
40+
// Prevent potential double requests if component re-renders quickly
41+
let createRequestSent = false; // Use a simple flag within the effect's scope
42+
if (!createRequestSent) {
43+
createRequestSent = true;
44+
requestData<ChatSession>('createNewChat')
45+
.then(newSession => {
46+
if (newSession?.id) {
47+
console.log(`[App] New session created: ${newSession.id}. Redirecting...`);
48+
router.open(`/chat/${newSession.id}`, true); // Redirect with replace
49+
} else {
50+
console.error('[App] Backend did not return a valid new session.');
51+
router.open('/chat/list'); // Fallback to list
52+
}
53+
})
54+
.catch(error => {
55+
console.error('[App] Error creating new chat:', error);
56+
router.open('/chat/list'); // Fallback to list
57+
});
58+
// Note: createRequestSent flag only prevents immediate re-trigger within the same render cycle.
59+
// If the component unmounts and remounts quickly on the same route, it might still trigger again.
60+
// A more robust solution might involve a store or ref if needed, but this handles simple cases.
61+
}
62+
}
63+
}, [page]); // Depend only on page
1764

18-
// Wait for the router 'page' object to be defined
19-
if (!page) {
20-
return (
21-
<div class="flex justify-center items-center h-screen text-gray-500 dark:text-gray-400">
22-
Initializing Router...
23-
</div>
24-
);
25-
}
65+
// --- Page Routing ---
66+
let CurrentPage: ComponentChild | VNode<any> | null = null;
2667

27-
// Determine which component to render based on the route name
28-
// '/' and '/chat/:chatId' now both render ChatPage
29-
if (page.route === 'home' || page.route === 'chat') {
30-
// Pass potential chatId from params. ChatPage will handle if it's undefined.
31-
const chatId = (page.route === 'chat' && page.params && page.params.chatId) ? page.params.chatId : undefined;
32-
// Use key={chatId || 'new'} to ensure component remounts when navigating between '/' and '/chat/:id'
33-
// or between different chat IDs. 'new' key for the initial '/' load.
34-
CurrentPage = <ChatPage key={chatId || 'new'} chatIdFromRoute={chatId} />;
68+
// Handle initial loading state or root path before redirect
69+
if (!page || page.path === '/') {
70+
CurrentPage = (
71+
<div class="flex justify-center items-center h-full bg-background text-foreground/70">
72+
<span class="i-carbon-circle-dash animate-spin mr-2"></span>
73+
Loading...
74+
</div>
75+
);
76+
} else if (page.route === 'chat') { // Specific chat
77+
const chatId = page.params.chatId;
78+
CurrentPage = <ChatPage key={chatId} chatIdFromRoute={chatId} />;
3579
} else if (page.route === 'settings') {
3680
CurrentPage = <SettingPage />;
37-
} else if (page.route === 'sessions') { // Add route for sessions
81+
} else if (page.route === 'chatList') { // Use new route name 'chatList'
3882
CurrentPage = <ChatListPage />;
39-
} else {
40-
// Fallback for unknown routes
41-
CurrentPage = <div class="p-6 text-center text-red-500">404: Page Not Found</div>;
83+
} else if (page.route === 'newChat') { // Display loading while creating
84+
CurrentPage = (
85+
<div class="flex justify-center items-center h-full bg-background text-foreground/70">
86+
<span class="i-carbon-circle-dash animate-spin mr-2"></span>
87+
Creating new chat...
88+
</div>
89+
);
90+
} else { // Fallback 404
91+
CurrentPage = <div class="p-6 text-center text-vscode-error">404: Page Not Found</div>;
4292
}
4393

4494
return (
45-
<div class="flex w-screen h-screen text-gray-900 dark:text-gray-100 overflow-hidden">
46-
<div class="flex-grow flex flex-col overflow-y-auto">
95+
// Main container: Single column flex layout
96+
<div class="flex flex-col w-screen h-screen bg-background text-foreground overflow-hidden font-sans text-vscode">
97+
{/* Main Content Area */}
98+
<main class="flex-1 flex flex-col overflow-y-auto">
4799
{CurrentPage}
48-
</div>
100+
</main>
49101
</div>
50102
);
51103
}

0 commit comments

Comments
 (0)