Add mock messages; fix CodeBlock ellipses bug

This commit is contained in:
Saoud Rizwan
2024-07-09 21:34:07 -04:00
parent 97faff3ba5
commit c0420b3e90
6 changed files with 431 additions and 168 deletions

View File

@@ -345,7 +345,7 @@ ${activeEditorContents}`
return `Changes applied to ${filePath}:\n${diffResult}` return `Changes applied to ${filePath}:\n${diffResult}`
} else { } else {
this.say("tool", JSON.stringify({ tool: "editedExistingFile", path: filePath } as ClaudeSayTool)) this.say("tool", JSON.stringify({ tool: "editedExistingFile", path: filePath, content: " " } as ClaudeSayTool))
return `Tool succeeded, however there were no changes detected to ${filePath}` return `Tool succeeded, however there were no changes detected to ${filePath}`
} }
} else { } else {

View File

@@ -6,6 +6,7 @@ import SettingsView from "./components/SettingsView"
import { ClaudeMessage, ExtensionMessage } from "@shared/ExtensionMessage" import { ClaudeMessage, ExtensionMessage } from "@shared/ExtensionMessage"
import WelcomeView from "./components/WelcomeView" import WelcomeView from "./components/WelcomeView"
import { vscode } from "./utilities/vscode" import { vscode } from "./utilities/vscode"
import { mockMessages } from "./utilities/mockMessages"
/* /*
The contents of webviews however are created when the webview becomes visible and destroyed when the webview is moved into the background. Any state inside the webview will be lost when the webview is moved to a background tab. The contents of webviews however are created when the webview becomes visible and destroyed when the webview is moved into the background. Any state inside the webview will be lost when the webview is moved to a background tab.
@@ -60,163 +61,6 @@ const App: React.FC = () => {
} }
}, []) }, [])
// dummy data for messages
const generateRandomTimestamp = (baseDate: Date, rangeInDays: number): number => {
const rangeInMs = rangeInDays * 24 * 60 * 60 * 1000 // convert days to milliseconds
const randomOffset = Math.floor(Math.random() * rangeInMs * 2) - rangeInMs // rangeInMs * 2 to have offset in both directions
return baseDate.getTime() + randomOffset
}
const baseDate = new Date("2024-07-08T00:00:00Z")
const messages: ClaudeMessage[] = [
{
type: "say",
say: "task",
text: "Starting task, this is my requeirements",
ts: generateRandomTimestamp(baseDate, 1),
},
{
type: "ask",
ask: "request_limit_reached",
text: "Request limit reached",
ts: generateRandomTimestamp(baseDate, 2),
},
{ type: "ask", ask: "followup", text: "Any additional questions?", ts: generateRandomTimestamp(baseDate, 3) },
{ type: "say", say: "error", text: "An error occurred", ts: generateRandomTimestamp(baseDate, 4) },
{ type: "say", say: "text", text: "Some general text", ts: generateRandomTimestamp(baseDate, 7) },
{ type: "say", say: "tool", text: "Using a tool", ts: generateRandomTimestamp(baseDate, 8) },
// First command sequence
{ type: "ask", ask: "command", text: "ls -l", ts: generateRandomTimestamp(baseDate, 9) },
{ type: "say", say: "command_output", text: "file1.txt", ts: generateRandomTimestamp(baseDate, 10) },
{
type: "say",
say: "api_req_started",
text: JSON.stringify({ request: "GET /api/data" }),
ts: generateRandomTimestamp(baseDate, 5),
},
{ type: "say", say: "command_output", text: "file2.txt", ts: generateRandomTimestamp(baseDate, 11) },
{ type: "say", say: "command_output", text: "directory1", ts: generateRandomTimestamp(baseDate, 12) },
{ type: "say", say: "text", text: "Interrupting text", ts: generateRandomTimestamp(baseDate, 13) },
{
type: "say",
say: "api_req_finished",
text: JSON.stringify({ cost: "GET /api/data" }),
ts: generateRandomTimestamp(baseDate, 6),
},
// Second command sequence
{ type: "ask", ask: "command", text: "pwd", ts: generateRandomTimestamp(baseDate, 14) },
{ type: "say", say: "command_output", text: "/home/user", ts: generateRandomTimestamp(baseDate, 15) },
{ type: "ask", ask: "completion_result", text: "Task completed", ts: generateRandomTimestamp(baseDate, 16) },
// Third command sequence (no output)
{ type: "ask", ask: "command", text: "echo Hello", ts: generateRandomTimestamp(baseDate, 17) },
// Testing combineApiRequests
{ type: "say", say: "text", text: "Final message", ts: generateRandomTimestamp(baseDate, 18) },
{ type: "ask", ask: "command", text: "ls -l", ts: generateRandomTimestamp(baseDate, 19) },
{ type: "say", say: "command_output", text: "file1.txt", ts: generateRandomTimestamp(baseDate, 20) },
{
type: "say",
say: "api_req_started",
text: JSON.stringify({ request: "GET /api/data" }),
ts: generateRandomTimestamp(baseDate, 23),
},
{ type: "say", say: "command_output", text: "file2.txt", ts: generateRandomTimestamp(baseDate, 24) },
{ type: "say", say: "text", text: "Some random text", ts: generateRandomTimestamp(baseDate, 25) },
{
type: "say",
say: "api_req_finished",
text: JSON.stringify({ cost: 0.005 }),
ts: generateRandomTimestamp(baseDate, 26),
},
{ type: "ask", ask: "command", text: "pwd", ts: generateRandomTimestamp(baseDate, 27) },
{ type: "say", say: "command_output", text: "/home/user", ts: generateRandomTimestamp(baseDate, 28) },
{
type: "say",
say: "api_req_started",
text: JSON.stringify({ request: "POST /api/update" }),
ts: generateRandomTimestamp(baseDate, 29),
},
{ type: "say", say: "text", text: "Final message", ts: generateRandomTimestamp(baseDate, 30) },
{
type: "say",
say: "text",
text: "Starting API requests",
ts: Date.now(),
},
{
type: "say",
say: "api_req_started",
text: JSON.stringify({
request: "GET /api/data1",
}),
ts: Date.now() + 1000,
},
{
type: "say",
say: "api_req_finished",
text: JSON.stringify({
tokensIn: 10,
tokensOut: 20,
cost: 0.002,
}),
ts: Date.now() + 2000,
},
{
type: "say",
say: "text",
text: "Processing data...",
ts: Date.now() + 3000,
},
{
type: "say",
say: "api_req_started",
text: JSON.stringify({
request: "POST /api/update1",
}),
ts: Date.now() + 4000,
},
{
type: "say",
say: "api_req_finished",
text: JSON.stringify({
tokensIn: 15,
tokensOut: 25,
cost: 0.003,
}),
ts: Date.now() + 5000,
},
{
type: "say",
say: "text",
text: "More processing...",
ts: Date.now() + 6000,
},
{
type: "say",
say: "api_req_started",
text: JSON.stringify({
request: "GET /api/data2",
}),
ts: Date.now() + 7000,
},
{
type: "say",
say: "api_req_finished",
text: JSON.stringify({
tokensIn: 5,
tokensOut: 15,
cost: 0.001,
}),
ts: Date.now() + 8000,
},
]
return ( return (
<> <>
{showWelcome ? ( {showWelcome ? (
@@ -230,7 +74,7 @@ const App: React.FC = () => {
onDone={() => setShowSettings(false)} onDone={() => setShowSettings(false)}
/> />
) : ( ) : (
<ChatView messages={messages} /> <ChatView messages={mockMessages} />
)} )}
</> </>
) )

View File

@@ -114,11 +114,7 @@ const ChatRow: React.FC<ChatRowProps> = ({ message }) => {
case "api_req_finished": case "api_req_finished":
return null // Hide this message type return null // Hide this message type
case "tool": case "tool":
//const tool = JSON.parse(message.text || "{}") as ClaudeSayTool const tool = JSON.parse(message.text || "{}") as ClaudeSayTool
const tool: ClaudeSayTool = {
tool: "editedExistingFile",
path: "/path/to/file",
}
const toolIcon = (name: string) => ( const toolIcon = (name: string) => (
<span <span
className={`codicon codicon-${name}`} className={`codicon codicon-${name}`}

View File

@@ -1,7 +1,8 @@
import React, { useState } from "react" import React, { useMemo, useState } from "react"
import SyntaxHighlighter from "react-syntax-highlighter" import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"
import { oneDark } from "react-syntax-highlighter/dist/esm/styles/prism" import { oneDark } from "react-syntax-highlighter/dist/esm/styles/prism"
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react" import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
import { getLanguageFromPath } from "../utilities/getLanguageFromPath"
/* /*
const vscodeSyntaxStyle: React.CSSProperties = { const vscodeSyntaxStyle: React.CSSProperties = {
backgroundColor: "var(--vscode-editor-background)", backgroundColor: "var(--vscode-editor-background)",
@@ -68,10 +69,24 @@ const CodeBlock = ({ code, diff, language, path }: CodeBlockProps) => {
const backgroundColor = oneDark['pre[class*="language-"]'].background as string const backgroundColor = oneDark['pre[class*="language-"]'].background as string
/*
We need to remove leading non-alphanumeric characters from the path in order for our leading ellipses trick to work.
^: Anchors the match to the start of the string.
[^a-zA-Z0-9]+: Matches one or more characters that are not alphanumeric.
The replace method removes these matched characters, effectively trimming the string up to the first alphanumeric character.
*/
const removeLeadingNonAlphanumeric = (path: string): string => path.replace(/^[^a-zA-Z0-9]+/, "")
const inferredLanguage = useMemo(() => language ?? (path ? getLanguageFromPath(path) : undefined), [path, language])
console.log(inferredLanguage)
return ( return (
<div <div
style={{ style={{
borderRadius: "3px", borderRadius: "3px",
marginRight: "2px",
backgroundColor: backgroundColor, backgroundColor: backgroundColor,
overflow: "hidden", // This ensures the inner scrollable area doesn't overflow the rounded corners overflow: "hidden", // This ensures the inner scrollable area doesn't overflow the rounded corners
}}> }}>
@@ -94,7 +109,7 @@ const CodeBlock = ({ code, diff, language, path }: CodeBlockProps) => {
direction: "rtl", direction: "rtl",
textAlign: "left", textAlign: "left",
}}> }}>
{path} {removeLeadingNonAlphanumeric(path) + "\u200E"}
</span> </span>
<VSCodeButton appearance="icon" aria-label="Toggle Code" onClick={() => setIsExpanded(!isExpanded)}> <VSCodeButton appearance="icon" aria-label="Toggle Code" onClick={() => setIsExpanded(!isExpanded)}>
<span className={`codicon codicon-chevron-${isExpanded ? "up" : "down"}`}></span> <span className={`codicon codicon-chevron-${isExpanded ? "up" : "down"}`}></span>
@@ -111,12 +126,14 @@ const CodeBlock = ({ code, diff, language, path }: CodeBlockProps) => {
}}> }}>
<SyntaxHighlighter <SyntaxHighlighter
wrapLines={false} wrapLines={false}
language={language} language={inferredLanguage}
style={oneDark} style={oneDark}
customStyle={{ customStyle={{
margin: 0, margin: 0,
padding: "6px 10px", padding: "6px 10px",
borderRadius: 0, borderRadius: 0,
fontSize: 'var(--vscode-editor-font-size)',
lineHeight: 'var(--vscode-editor-line-height)'
}} }}
lineProps={ lineProps={
diff != null diff != null

View File

@@ -0,0 +1,90 @@
const extensionToLanguage: { [key: string]: string } = {
// Web technologies
html: "html",
htm: "html",
css: "css",
js: "javascript",
jsx: "jsx",
ts: "typescript",
tsx: "tsx",
// Backend languages
py: "python",
rb: "ruby",
php: "php",
java: "java",
cs: "csharp",
go: "go",
rs: "rust",
scala: "scala",
kt: "kotlin",
swift: "swift",
// Markup and data
json: "json",
xml: "xml",
yaml: "yaml",
yml: "yaml",
md: "markdown",
csv: "csv",
// Shell and scripting
sh: "bash",
bash: "bash",
zsh: "bash",
ps1: "powershell",
// Configuration
toml: "toml",
ini: "ini",
cfg: "ini",
conf: "ini",
// Other
sql: "sql",
graphql: "graphql",
gql: "graphql",
tex: "latex",
svg: "svg",
txt: "text",
// C-family languages
c: "c",
cpp: "cpp",
h: "c",
hpp: "cpp",
// Functional languages
hs: "haskell",
lhs: "haskell",
elm: "elm",
clj: "clojure",
cljs: "clojure",
erl: "erlang",
ex: "elixir",
exs: "elixir",
// Mobile development
dart: "dart",
m: "objectivec",
mm: "objectivec",
// Game development
lua: "lua",
gd: "gdscript", // Godot
unity: "csharp", // Unity (using C#)
// Data science and ML
r: "r",
jl: "julia",
ipynb: "jupyter", // Jupyter notebooks
}
// Example usage:
// console.log(getLanguageFromPath('/path/to/file.js')); // Output: javascript
export function getLanguageFromPath(path: string): string | undefined {
const extension = path.split(".").pop()?.toLowerCase() || ""
console.log(`converted path to language: ${path} -> ${extensionToLanguage[extension]}`)
return extensionToLanguage[extension]
}

View File

@@ -0,0 +1,316 @@
import { ClaudeMessage } from "@shared/ExtensionMessage";
export const mockMessages: ClaudeMessage[] = [
{
ts: Date.now() - 3600000,
type: "say",
say: "task",
text: "Create a React component for a todo list application",
},
{
ts: Date.now() - 3500000,
type: "say",
say: "api_req_started",
text: JSON.stringify({
request: {
text: "Create a React component for a todo list application",
type: "text",
},
tokensIn: 10,
tokensOut: 250,
cost: 0.0002,
}),
},
{
ts: Date.now() - 3300000,
type: "say",
say: "text",
text: "Here's a basic React component for a todo list application:",
},
{
ts: Date.now() - 3200000,
type: "say",
say: "tool",
text: JSON.stringify({
tool: "newFileCreated",
path: "/src/components/TodoList.tsx",
content: `import React, { useState } from 'react';
interface Todo {
id: number;
text: string;
completed: boolean;
}
const TodoList: React.FC = () => {
const [todos, setTodos] = useState<Todo[]>([]);
const [inputValue, setInputValue] = useState('');
const addTodo = () => {
if (inputValue.trim() !== '') {
setTodos([...todos, { id: Date.now(), text: inputValue, completed: false }]);
setInputValue('');
}
};
const toggleTodo = (id: number) => {
setTodos(todos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
));
};
return (
<div>
<h1>Todo List</h1>
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder="Add a new todo"
/>
<button onClick={addTodo}>Add</button>
<ul>
{todos.map(todo => (
<li
key={todo.id}
onClick={() => toggleTodo(todo.id)}
style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
>
{todo.text}
</li>
))}
</ul>
</div>
);
};
export default TodoList;`,
}),
},
{
ts: Date.now() - 3100000,
type: "say",
say: "text",
text: "I've created a new file 'TodoList.tsx' in the '/src/components/' directory. This component includes basic functionality for adding and toggling todos. You can further customize and style it as needed.",
},
{
ts: Date.now() - 3000000,
type: "ask",
ask: "followup",
text: "Do you want me to explain the code or add any additional features to the todo list?",
},
{
ts: Date.now() - 2900000,
type: "say",
say: "text",
text: "Let's add a feature to delete todos from the list.",
},
{
ts: Date.now() - 2800000,
type: "say",
say: "api_req_started",
text: JSON.stringify({
request: {
text: "Add a feature to delete todos from the list",
type: "text",
},
tokensIn: 8,
tokensOut: 180,
cost: 0.0003,
}),
},
{
ts: Date.now() - 2600000,
type: "say",
say: "tool",
text: JSON.stringify({
tool: "editedExistingFile",
path: "/src/components/TodoList.tsx",
diff: `@@ -14,6 +14,11 @@
}
};
+ const deleteTodo = (id: number) => {
+ setTodos(todos.filter(todo => todo.id !== id));
+ };
+
const toggleTodo = (id: number) => {
setTodos(todos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
@@ -35,6 +40,7 @@
style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
>
{todo.text}
+ <button onClick={() => deleteTodo(todo.id)}>Delete</button>
</li>
))}
</ul>`,
}),
},
{
ts: Date.now() - 2500000,
type: "say",
say: "text",
text: "I've updated the TodoList component to include a delete feature. Each todo item now has a 'Delete' button that removes the todo from the list when clicked.",
},
{
ts: Date.now() - 2400000,
type: "ask",
ask: "command",
text: "npm run test\n\nOutput:\nPASS src/__tests__/TodoList.test.tsx\n TodoList Component\n ✓ renders without crashing (23 ms)\n ✓ adds a new todo when the add button is clicked (34 ms)\n ✓ toggles a todo when clicked (45 ms)\n ✓ deletes a todo when the delete button is clicked (28 ms)\n\nTest Suites: 1 passed, 1 total\nTests: 4 passed, 4 total\nSnapshots: 0 total\nTime: 1.234 s\nRan all test suites.",
},
{
ts: Date.now() - 2300000,
type: "say",
say: "text",
text: "Great! The tests for the TodoList component have passed. All functionalities, including the new delete feature, are working as expected.",
},
{
ts: Date.now() - 2200000,
type: "ask",
ask: "request_limit_reached",
text: "You've reached the maximum number of requests for this task. Would you like to continue or start a new task?",
},
{
ts: Date.now() - 2100000,
type: "say",
say: "text",
text: "Let's start a new task. What would you like to work on next?",
},
{
ts: Date.now() - 2000000,
type: "say",
say: "task",
text: "Create a simple API using Express.js",
},
{
ts: Date.now() - 1900000,
type: "say",
say: "api_req_started",
text: JSON.stringify({
request: {
text: "Create a simple API using Express.js",
type: "text",
},
tokensIn: 7,
tokensOut: 220,
cost: 0.0002,
}),
},
{
ts: Date.now() - 1700000,
type: "say",
say: "tool",
text: JSON.stringify({
tool: "newFileCreated",
path: "/src/app.js",
content: `const express = require('express');
const app = express();
const port = 3000;
app.use(express.json());
let items = [];
app.get('/items', (req, res) => {
res.json(items);
});
app.post('/items', (req, res) => {
const newItem = req.body;
items.push(newItem);
res.status(201).json(newItem);
});
app.get('/items/:id', (req, res) => {
const item = items.find(i => i.id === parseInt(req.params.id));
if (item) {
res.json(item);
} else {
res.status(404).send('Item not found');
}
});
app.listen(port, () => {
console.log(\`API running on http://localhost:\${port}\`);
});`,
}),
},
{
ts: Date.now() - 1600000,
type: "say",
say: "text",
text: "I've created a simple Express.js API with endpoints for getting all items, adding a new item, and getting a specific item by ID. The API is set up to run on port 3000.",
},
{
ts: Date.now() - 1500000,
type: "ask",
ask: "command",
text: "npm install express\n\nOutput:\nadded 57 packages, and audited 58 packages in 3s\n\n7 packages are looking for funding\n run `npm fund` for details\n\nfound 0 vulnerabilities",
},
{
ts: Date.now() - 1400000,
type: "say",
say: "text",
text: "Express has been successfully installed. You can now run the API using 'node app.js' in the terminal.",
},
{
ts: Date.now() - 1300000,
type: "ask",
ask: "completion_result",
text: "The API has been successfully created and set up. Is there anything else you'd like me to do with this API?",
},
{
ts: Date.now() - 1200000,
type: "say",
say: "error",
text: "An error occurred while trying to start the server: EADDRINUSE: address already in use :::3000",
},
{
ts: Date.now() - 1100000,
type: "say",
say: "text",
text: "It seems that port 3000 is already in use. Let's modify the code to use a different port.",
},
{
ts: Date.now() - 1000000,
type: "say",
say: "tool",
text: JSON.stringify({
tool: "editedExistingFile",
path: "/src/app.js",
diff: `@@ -1,6 +1,6 @@
const express = require('express');
const app = express();
-const port = 3000;
+const port = process.env.PORT || 3001;
app.use(express.json());
`,
}),
},
{
ts: Date.now() - 900000,
type: "say",
say: "text",
text: "I've updated the code to use port 3001 if port 3000 is not available. You can now try running the server again.",
},
{
ts: Date.now() - 800000,
type: "ask",
ask: "command",
text: "node app.js\n\nOutput:\nAPI running on http://localhost:3001",
},
{
ts: Date.now() - 700000,
type: "say",
say: "text",
text: "Great! The API is now running successfully on port 3001.",
},
{
ts: Date.now() - 600000,
type: "ask",
ask: "completion_result",
text: "The API has been created, set up, and is now running successfully. Is there anything else you'd like me to do?",
},
]