Skip to content

Commit f624850

Browse files
committed
feat: Add history functionality to track and display agent actions
- Add History view to display previous agent actions - Implement history storage in VSCode global state - Add clear history functionality with user confirmation - Create history components and manager - Update routing to include history view - Add history command handlers in extension
1 parent 482b7b7 commit f624850

File tree

6 files changed

+209
-9
lines changed

6 files changed

+209
-9
lines changed

src/extension.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,12 @@ import {
1616
import { sidecarUseSelfRun } from './utilities/sidecarUrl';
1717
import { getUniqueId } from './utilities/uniqueId';
1818
import { ProjectContext } from './utilities/workspaceContext';
19+
import { Exchange } from './model';
1920

2021
export let SIDECAR_CLIENT: SideCarClient | null = null;
2122

23+
const HISTORY_STORAGE_KEY = 'codestory.history';
24+
2225
/**
2326
Extension → PanelProvider → Webview (app.tsx)
2427
(native) (bridge) (UI layer)
@@ -60,6 +63,25 @@ export async function activate(context: vscode.ExtensionContext) {
6063
RepoRefBackend.local
6164
);
6265

66+
// Register history-related commands
67+
context.subscriptions.push(
68+
vscode.commands.registerCommand('codestory.saveHistory', async (exchanges: Exchange[]) => {
69+
await context.globalState.update(HISTORY_STORAGE_KEY, exchanges);
70+
})
71+
);
72+
73+
context.subscriptions.push(
74+
vscode.commands.registerCommand('codestory.loadHistory', async () => {
75+
return context.globalState.get<Exchange[]>(HISTORY_STORAGE_KEY) || [];
76+
})
77+
);
78+
79+
context.subscriptions.push(
80+
vscode.commands.registerCommand('codestory.clearHistory', async () => {
81+
await context.globalState.update(HISTORY_STORAGE_KEY, []);
82+
})
83+
);
84+
6385
// We also get some context about the workspace we are in and what we are
6486
// upto
6587
const projectContext = new ProjectContext();

src/model.ts

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export enum View {
55
Preset = 'preset',
66
Welcome = 'welcome',
77
Settings = 'settings',
8-
// History = 'history',
8+
History = 'history',
99
}
1010

1111
export type ViewType = `${View}`;
@@ -345,10 +345,10 @@ export type Exchange = Request | Response;
345345

346346
export enum Provider {
347347
Anthropic = 'anthropic',
348-
//OpenAI = 'open-ai',
348+
OpenAI = 'openai',
349349
OpenRouter = 'open-router',
350350
//GoogleGemini = 'google-gemini',
351-
//AWSBedrock = 'aws-bedrock',
351+
AWSBedrock = 'aws-bedrock',
352352
OpenAICompatible = 'openai-compatible',
353353
//Ollama = 'ollama',
354354
}
@@ -359,7 +359,25 @@ export enum AnthropicModels {
359359
ClaudeOpus = 'ClaudeOpus',
360360
}
361361

362-
export type Models = `${AnthropicModels}`;
362+
export enum OpenAIModels {
363+
GPT4 = 'gpt-4',
364+
GPT4Turbo = 'gpt-4-turbo-preview',
365+
GPT35Turbo = 'gpt-3.5-turbo',
366+
GPT4O = 'gpt-4o',
367+
GPT4OMini = 'gpt-4o-mini',
368+
O1Preview = 'o1-preview',
369+
O1Mini = 'o1-mini'
370+
}
371+
372+
export enum AWSBedrockModels {
373+
Claude3Sonnet = 'anthropic.claude-3-sonnet-20240229-v1:0',
374+
Claude3Haiku = 'anthropic.claude-3-haiku-20240307-v1:0',
375+
Claude2 = 'anthropic.claude-v2:1',
376+
Titan = 'amazon.titan-text-express-v1',
377+
Llama2 = 'meta.llama2-70b-chat-v1',
378+
}
379+
380+
export type Models = `${AnthropicModels}` | `${OpenAIModels}` | `${AWSBedrockModels}`;
363381

364382
export enum PermissionState {
365383
Always = 'always',

src/utilities/historyManager.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import * as vscode from 'vscode';
2+
import { Exchange } from '../model';
3+
4+
export class HistoryManager {
5+
private static readonly HISTORY_KEY = 'codestory.history';
6+
7+
static async saveHistory(exchanges: Exchange[]): Promise<void> {
8+
try {
9+
await vscode.commands.executeCommand('codestory.saveHistory', exchanges);
10+
} catch (error) {
11+
console.error('Failed to save history:', error);
12+
}
13+
}
14+
15+
static async loadHistory(): Promise<Exchange[]> {
16+
try {
17+
const history = await vscode.commands.executeCommand<Exchange[]>('codestory.loadHistory');
18+
return history || [];
19+
} catch (error) {
20+
console.error('Failed to load history:', error);
21+
return [];
22+
}
23+
}
24+
25+
static async clearHistory(): Promise<void> {
26+
try {
27+
await vscode.commands.executeCommand('codestory.clearHistory');
28+
} catch (error) {
29+
console.error('Failed to clear history:', error);
30+
}
31+
}
32+
33+
static async clearHistoryWithPermission(): Promise<boolean> {
34+
try {
35+
const result = await vscode.window.showWarningMessage(
36+
'Are you sure you want to clear the history?',
37+
{ modal: true },
38+
'Yes',
39+
'No'
40+
);
41+
return result === 'Yes';
42+
} catch (error) {
43+
console.error('Failed to show confirmation dialog:', error);
44+
return false;
45+
}
46+
}
47+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import * as React from 'react';
2+
import { History } from '../components/history';
3+
import { HistoryManager } from '../../utilities/historyManager';
4+
import { useNavigate } from 'react-router-dom';
5+
import { Exchange } from '../../model';
6+
import * as vscode from 'vscode';
7+
8+
export const HistoryView: React.FC = () => {
9+
const [exchanges, setExchanges] = React.useState<Exchange[]>([]);
10+
const [loading, setLoading] = React.useState(true);
11+
const navigate = useNavigate();
12+
13+
React.useEffect(() => {
14+
const loadHistory = async () => {
15+
try {
16+
setLoading(true);
17+
const loadedHistory = await HistoryManager.loadHistory();
18+
setExchanges(loadedHistory);
19+
} catch (error) {
20+
void vscode.window.showErrorMessage('Failed to load history');
21+
} finally {
22+
setLoading(false);
23+
}
24+
};
25+
void loadHistory();
26+
}, []);
27+
28+
const handleClearHistory = async () => {
29+
try {
30+
const confirmed = await HistoryManager.clearHistoryWithPermission();
31+
if (confirmed) {
32+
await HistoryManager.clearHistory();
33+
setExchanges([]);
34+
navigate('/task'); // Navigate back to task view after clearing
35+
}
36+
} catch (error) {
37+
void vscode.window.showErrorMessage('Failed to clear history');
38+
}
39+
};
40+
41+
if (loading) {
42+
return (
43+
<div className="flex items-center justify-center h-full">
44+
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900"></div>
45+
</div>
46+
);
47+
}
48+
49+
return (
50+
<div className="flex flex-col h-full">
51+
<History
52+
exchanges={exchanges}
53+
onClearHistory={handleClearHistory}
54+
/>
55+
</div>
56+
);
57+
};
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import * as React from 'react';
2+
import { Exchange, Response, Request } from '../../model';
3+
import MarkdownRenderer from './markdown-renderer';
4+
5+
interface HistoryProps {
6+
exchanges: Exchange[];
7+
onClearHistory: () => void;
8+
}
9+
10+
export const History: React.FC<HistoryProps> = ({ exchanges, onClearHistory }: HistoryProps) => {
11+
return (
12+
<div className="flex flex-col gap-4 p-4">
13+
<div className="flex justify-between items-center">
14+
<h2 className="text-xl font-semibold">Session History</h2>
15+
<button
16+
onClick={onClearHistory}
17+
className="px-3 py-1 bg-red-500 text-white rounded hover:bg-red-600 transition-colors"
18+
>
19+
Clear History
20+
</button>
21+
</div>
22+
<div className="flex flex-col gap-4">
23+
{exchanges.map((exchange: Exchange, index: number) => (
24+
<div key={index} className="border rounded-lg p-4 bg-gray-50">
25+
{exchange.type === 'request' ? (
26+
<div className="flex flex-col gap-2">
27+
<div className="font-medium text-blue-600">User Request:</div>
28+
<div className="pl-4">{(exchange as Request).message}</div>
29+
</div>
30+
) : (
31+
<div className="flex flex-col gap-2">
32+
<div className="font-medium text-green-600">Assistant Response:</div>
33+
<div className="pl-4">
34+
{(exchange as Response).parts.map((part, partIndex) => {
35+
if (part.type === 'markdown') {
36+
return (
37+
<MarkdownRenderer
38+
key={partIndex}
39+
rawMarkdown={part.rawMarkdown}
40+
/>
41+
);
42+
}
43+
return null;
44+
})}
45+
</div>
46+
</div>
47+
)}
48+
</div>
49+
))}
50+
</div>
51+
</div>
52+
);
53+
};

src/webviews/routes.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import { createMemoryRouter, useLocation, useNavigate } from 'react-router-dom';
55
import { View, Event } from '../model';
66
import { loadSettings, SettingsView } from '@settings/settings-view';
77
import * as React from 'react';
8+
import { HistoryView } from './@history/history-view';
9+
10+
declare const vscode: any;
811

912
export const router = createMemoryRouter(
1013
[
@@ -20,10 +23,10 @@ export const router = createMemoryRouter(
2023
path: View.Task,
2124
element: <TaskView />,
2225
},
23-
// {
24-
// path: View.History,
25-
// element: <TaskView />,
26-
// },
26+
{
27+
path: View.History,
28+
element: <HistoryView />,
29+
},
2730
{
2831
path: View.Settings,
2932
loader: loadSettings,
@@ -56,7 +59,7 @@ export function useNavigationFromExtension() {
5659
return () => {
5760
window.removeEventListener('message', handleMessage);
5861
};
59-
}, []);
62+
}, [navigate]);
6063

6164
// workaround to start a new task
6265
React.useEffect(() => {

0 commit comments

Comments
 (0)