mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-02-28 03:27:40 +00:00
Compare commits
2 Commits
v1.13.0
...
babe96eedd
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
babe96eedd | ||
|
|
60c8bda755 |
@@ -489,7 +489,7 @@
|
|||||||
<span class="endpoint-path"><span class="api-url">http://localhost:3001</span>/api/agent</span>
|
<span class="endpoint-path"><span class="api-url">http://localhost:3001</span>/api/agent</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p>Trigger an AI agent (Claude or Cursor) to work on a project.</p>
|
<p>Trigger an AI agent (Claude, Cursor, or Codex) to work on a project.</p>
|
||||||
|
|
||||||
<h4>Request Body Parameters</h4>
|
<h4>Request Body Parameters</h4>
|
||||||
<table>
|
<table>
|
||||||
@@ -524,7 +524,7 @@
|
|||||||
<td><code>provider</code></td>
|
<td><code>provider</code></td>
|
||||||
<td>string</td>
|
<td>string</td>
|
||||||
<td><span class="badge badge-optional">Optional</span></td>
|
<td><span class="badge badge-optional">Optional</span></td>
|
||||||
<td><code>claude</code> or <code>cursor</code> (default: <code>claude</code>)</td>
|
<td><code>claude</code>, <code>cursor</code>, or <code>codex</code> (default: <code>claude</code>)</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>stream</code></td>
|
<td><code>stream</code></td>
|
||||||
@@ -536,7 +536,9 @@
|
|||||||
<td><code>model</code></td>
|
<td><code>model</code></td>
|
||||||
<td>string</td>
|
<td>string</td>
|
||||||
<td><span class="badge badge-optional">Optional</span></td>
|
<td><span class="badge badge-optional">Optional</span></td>
|
||||||
<td>Model to use (for Cursor)</td>
|
<td id="model-options-cell">
|
||||||
|
Model identifier for the AI provider (loading from constants...)
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>cleanup</code></td>
|
<td><code>cleanup</code></td>
|
||||||
@@ -818,31 +820,51 @@ data: {"type":"done"}</code></pre>
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script type="module">
|
||||||
|
// Import model constants
|
||||||
|
import { CLAUDE_MODELS, CURSOR_MODELS, CODEX_MODELS } from '/shared/modelConstants.js';
|
||||||
|
|
||||||
// Dynamic URL replacement
|
// Dynamic URL replacement
|
||||||
const apiUrl = window.location.origin;
|
const apiUrl = window.location.origin;
|
||||||
document.querySelectorAll('.api-url').forEach(el => {
|
document.querySelectorAll('.api-url').forEach(el => {
|
||||||
el.textContent = apiUrl;
|
el.textContent = apiUrl;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Dynamically populate model documentation
|
||||||
|
window.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const modelCell = document.getElementById('model-options-cell');
|
||||||
|
if (modelCell) {
|
||||||
|
const claudeModels = CLAUDE_MODELS.OPTIONS.map(m => `<code>${m.value}</code>`).join(', ');
|
||||||
|
const cursorModels = CURSOR_MODELS.OPTIONS.slice(0, 8).map(m => `<code>${m.value}</code>`).join(', ');
|
||||||
|
const codexModels = CODEX_MODELS.OPTIONS.map(m => `<code>${m.value}</code>`).join(', ');
|
||||||
|
|
||||||
|
modelCell.innerHTML = `
|
||||||
|
Model identifier for the AI provider:<br><br>
|
||||||
|
<strong>Claude:</strong> ${claudeModels} (default: <code>${CLAUDE_MODELS.DEFAULT}</code>)<br><br>
|
||||||
|
<strong>Cursor:</strong> ${cursorModels}, and more (default: <code>${CURSOR_MODELS.DEFAULT}</code>)<br><br>
|
||||||
|
<strong>Codex:</strong> ${codexModels} (default: <code>${CODEX_MODELS.DEFAULT}</code>)
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Tab switching
|
// Tab switching
|
||||||
function showTab(tabName) {
|
window.showTab = function(tabName) {
|
||||||
const parentBlock = event.target.closest('.example-block');
|
const parentBlock = event.target.closest('.example-block');
|
||||||
if (!parentBlock) return;
|
if (!parentBlock) return;
|
||||||
|
|
||||||
parentBlock.querySelectorAll('.tab-content').forEach(tab => {
|
parentBlock.querySelectorAll('.tab-content').forEach(tab => {
|
||||||
tab.classList.remove('active');
|
tab.classList.remove('active');
|
||||||
});
|
});
|
||||||
parentBlock.querySelectorAll('.tab-button').forEach(btn => {
|
parentBlock.querySelectorAll('.tab-button').forEach(btn => {
|
||||||
btn.classList.remove('active');
|
btn.classList.remove('active');
|
||||||
});
|
});
|
||||||
|
|
||||||
const targetTab = parentBlock.querySelector('#' + tabName);
|
const targetTab = parentBlock.querySelector('#' + tabName);
|
||||||
if (targetTab) {
|
if (targetTab) {
|
||||||
targetTab.classList.add('active');
|
targetTab.classList.add('active');
|
||||||
event.target.classList.add('active');
|
event.target.classList.add('active');
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Prism.js -->
|
<!-- Prism.js -->
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { query } from '@anthropic-ai/claude-agent-sdk';
|
|||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
|
import { CLAUDE_MODELS } from '../shared/modelConstants.js';
|
||||||
|
|
||||||
// Session tracking: Map of session IDs to active query instances
|
// Session tracking: Map of session IDs to active query instances
|
||||||
const activeSessions = new Map();
|
const activeSessions = new Map();
|
||||||
@@ -77,7 +78,7 @@ function mapCliOptionsToSDK(options = {}) {
|
|||||||
|
|
||||||
// Map model (default to sonnet)
|
// Map model (default to sonnet)
|
||||||
// Valid models: sonnet, opus, haiku, opusplan, sonnet[1m]
|
// Valid models: sonnet, opus, haiku, opusplan, sonnet[1m]
|
||||||
sdkOptions.model = options.model || 'sonnet';
|
sdkOptions.model = options.model || CLAUDE_MODELS.DEFAULT;
|
||||||
console.log(`Using model: ${sdkOptions.model}`);
|
console.log(`Using model: ${sdkOptions.model}`);
|
||||||
|
|
||||||
// Map system prompt configuration
|
// Map system prompt configuration
|
||||||
@@ -397,10 +398,10 @@ async function queryClaudeSDK(command, options = {}, ws) {
|
|||||||
// Send session-created event only once for new sessions
|
// Send session-created event only once for new sessions
|
||||||
if (!sessionId && !sessionCreatedSent) {
|
if (!sessionId && !sessionCreatedSent) {
|
||||||
sessionCreatedSent = true;
|
sessionCreatedSent = true;
|
||||||
ws.send(JSON.stringify({
|
ws.send({
|
||||||
type: 'session-created',
|
type: 'session-created',
|
||||||
sessionId: capturedSessionId
|
sessionId: capturedSessionId
|
||||||
}));
|
});
|
||||||
} else {
|
} else {
|
||||||
console.log('Not sending session-created. sessionId:', sessionId, 'sessionCreatedSent:', sessionCreatedSent);
|
console.log('Not sending session-created. sessionId:', sessionId, 'sessionCreatedSent:', sessionCreatedSent);
|
||||||
}
|
}
|
||||||
@@ -410,20 +411,20 @@ async function queryClaudeSDK(command, options = {}, ws) {
|
|||||||
|
|
||||||
// Transform and send message to WebSocket
|
// Transform and send message to WebSocket
|
||||||
const transformedMessage = transformMessage(message);
|
const transformedMessage = transformMessage(message);
|
||||||
ws.send(JSON.stringify({
|
ws.send({
|
||||||
type: 'claude-response',
|
type: 'claude-response',
|
||||||
data: transformedMessage
|
data: transformedMessage
|
||||||
}));
|
});
|
||||||
|
|
||||||
// Extract and send token budget updates from result messages
|
// Extract and send token budget updates from result messages
|
||||||
if (message.type === 'result') {
|
if (message.type === 'result') {
|
||||||
const tokenBudget = extractTokenBudget(message);
|
const tokenBudget = extractTokenBudget(message);
|
||||||
if (tokenBudget) {
|
if (tokenBudget) {
|
||||||
console.log('Token budget from modelUsage:', tokenBudget);
|
console.log('Token budget from modelUsage:', tokenBudget);
|
||||||
ws.send(JSON.stringify({
|
ws.send({
|
||||||
type: 'token-budget',
|
type: 'token-budget',
|
||||||
data: tokenBudget
|
data: tokenBudget
|
||||||
}));
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -438,12 +439,12 @@ async function queryClaudeSDK(command, options = {}, ws) {
|
|||||||
|
|
||||||
// Send completion event
|
// Send completion event
|
||||||
console.log('Streaming complete, sending claude-complete event');
|
console.log('Streaming complete, sending claude-complete event');
|
||||||
ws.send(JSON.stringify({
|
ws.send({
|
||||||
type: 'claude-complete',
|
type: 'claude-complete',
|
||||||
sessionId: capturedSessionId,
|
sessionId: capturedSessionId,
|
||||||
exitCode: 0,
|
exitCode: 0,
|
||||||
isNewSession: !sessionId && !!command
|
isNewSession: !sessionId && !!command
|
||||||
}));
|
});
|
||||||
console.log('claude-complete event sent');
|
console.log('claude-complete event sent');
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -458,10 +459,10 @@ async function queryClaudeSDK(command, options = {}, ws) {
|
|||||||
await cleanupTempFiles(tempImagePaths, tempDir);
|
await cleanupTempFiles(tempImagePaths, tempDir);
|
||||||
|
|
||||||
// Send error to WebSocket
|
// Send error to WebSocket
|
||||||
ws.send(JSON.stringify({
|
ws.send({
|
||||||
type: 'claude-error',
|
type: 'claude-error',
|
||||||
error: error.message
|
error: error.message
|
||||||
}));
|
});
|
||||||
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,29 +102,29 @@ async function spawnCursor(command, options = {}, ws) {
|
|||||||
// Send session-created event only once for new sessions
|
// Send session-created event only once for new sessions
|
||||||
if (!sessionId && !sessionCreatedSent) {
|
if (!sessionId && !sessionCreatedSent) {
|
||||||
sessionCreatedSent = true;
|
sessionCreatedSent = true;
|
||||||
ws.send(JSON.stringify({
|
ws.send({
|
||||||
type: 'session-created',
|
type: 'session-created',
|
||||||
sessionId: capturedSessionId,
|
sessionId: capturedSessionId,
|
||||||
model: response.model,
|
model: response.model,
|
||||||
cwd: response.cwd
|
cwd: response.cwd
|
||||||
}));
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send system info to frontend
|
// Send system info to frontend
|
||||||
ws.send(JSON.stringify({
|
ws.send({
|
||||||
type: 'cursor-system',
|
type: 'cursor-system',
|
||||||
data: response
|
data: response
|
||||||
}));
|
});
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'user':
|
case 'user':
|
||||||
// Forward user message
|
// Forward user message
|
||||||
ws.send(JSON.stringify({
|
ws.send({
|
||||||
type: 'cursor-user',
|
type: 'cursor-user',
|
||||||
data: response
|
data: response
|
||||||
}));
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'assistant':
|
case 'assistant':
|
||||||
@@ -134,7 +134,7 @@ async function spawnCursor(command, options = {}, ws) {
|
|||||||
messageBuffer += textContent;
|
messageBuffer += textContent;
|
||||||
|
|
||||||
// Send as Claude-compatible format for frontend
|
// Send as Claude-compatible format for frontend
|
||||||
ws.send(JSON.stringify({
|
ws.send({
|
||||||
type: 'claude-response',
|
type: 'claude-response',
|
||||||
data: {
|
data: {
|
||||||
type: 'content_block_delta',
|
type: 'content_block_delta',
|
||||||
@@ -143,7 +143,7 @@ async function spawnCursor(command, options = {}, ws) {
|
|||||||
text: textContent
|
text: textContent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
});
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -153,37 +153,37 @@ async function spawnCursor(command, options = {}, ws) {
|
|||||||
|
|
||||||
// Send final message if we have buffered content
|
// Send final message if we have buffered content
|
||||||
if (messageBuffer) {
|
if (messageBuffer) {
|
||||||
ws.send(JSON.stringify({
|
ws.send({
|
||||||
type: 'claude-response',
|
type: 'claude-response',
|
||||||
data: {
|
data: {
|
||||||
type: 'content_block_stop'
|
type: 'content_block_stop'
|
||||||
}
|
}
|
||||||
}));
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send completion event
|
// Send completion event
|
||||||
ws.send(JSON.stringify({
|
ws.send({
|
||||||
type: 'cursor-result',
|
type: 'cursor-result',
|
||||||
sessionId: capturedSessionId || sessionId,
|
sessionId: capturedSessionId || sessionId,
|
||||||
data: response,
|
data: response,
|
||||||
success: response.subtype === 'success'
|
success: response.subtype === 'success'
|
||||||
}));
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// Forward any other message types
|
// Forward any other message types
|
||||||
ws.send(JSON.stringify({
|
ws.send({
|
||||||
type: 'cursor-response',
|
type: 'cursor-response',
|
||||||
data: response
|
data: response
|
||||||
}));
|
});
|
||||||
}
|
}
|
||||||
} catch (parseError) {
|
} catch (parseError) {
|
||||||
console.log('📄 Non-JSON response:', line);
|
console.log('📄 Non-JSON response:', line);
|
||||||
// If not JSON, send as raw text
|
// If not JSON, send as raw text
|
||||||
ws.send(JSON.stringify({
|
ws.send({
|
||||||
type: 'cursor-output',
|
type: 'cursor-output',
|
||||||
data: line
|
data: line
|
||||||
}));
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -191,10 +191,10 @@ async function spawnCursor(command, options = {}, ws) {
|
|||||||
// Handle stderr
|
// Handle stderr
|
||||||
cursorProcess.stderr.on('data', (data) => {
|
cursorProcess.stderr.on('data', (data) => {
|
||||||
console.error('Cursor CLI stderr:', data.toString());
|
console.error('Cursor CLI stderr:', data.toString());
|
||||||
ws.send(JSON.stringify({
|
ws.send({
|
||||||
type: 'cursor-error',
|
type: 'cursor-error',
|
||||||
error: data.toString()
|
error: data.toString()
|
||||||
}));
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle process completion
|
// Handle process completion
|
||||||
@@ -205,12 +205,12 @@ async function spawnCursor(command, options = {}, ws) {
|
|||||||
const finalSessionId = capturedSessionId || sessionId || processKey;
|
const finalSessionId = capturedSessionId || sessionId || processKey;
|
||||||
activeCursorProcesses.delete(finalSessionId);
|
activeCursorProcesses.delete(finalSessionId);
|
||||||
|
|
||||||
ws.send(JSON.stringify({
|
ws.send({
|
||||||
type: 'claude-complete',
|
type: 'claude-complete',
|
||||||
sessionId: finalSessionId,
|
sessionId: finalSessionId,
|
||||||
exitCode: code,
|
exitCode: code,
|
||||||
isNewSession: !sessionId && !!command // Flag to indicate this was a new session
|
isNewSession: !sessionId && !!command // Flag to indicate this was a new session
|
||||||
}));
|
});
|
||||||
|
|
||||||
if (code === 0) {
|
if (code === 0) {
|
||||||
resolve();
|
resolve();
|
||||||
@@ -226,12 +226,12 @@ async function spawnCursor(command, options = {}, ws) {
|
|||||||
// Clean up process reference on error
|
// Clean up process reference on error
|
||||||
const finalSessionId = capturedSessionId || sessionId || processKey;
|
const finalSessionId = capturedSessionId || sessionId || processKey;
|
||||||
activeCursorProcesses.delete(finalSessionId);
|
activeCursorProcesses.delete(finalSessionId);
|
||||||
|
|
||||||
ws.send(JSON.stringify({
|
ws.send({
|
||||||
type: 'cursor-error',
|
type: 'cursor-error',
|
||||||
error: error.message
|
error: error.message
|
||||||
}));
|
});
|
||||||
|
|
||||||
reject(error);
|
reject(error);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -717,6 +717,32 @@ wss.on('connection', (ws, request) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WebSocket Writer - Wrapper for WebSocket to match SSEStreamWriter interface
|
||||||
|
*/
|
||||||
|
class WebSocketWriter {
|
||||||
|
constructor(ws) {
|
||||||
|
this.ws = ws;
|
||||||
|
this.sessionId = null;
|
||||||
|
this.isWebSocketWriter = true; // Marker for transport detection
|
||||||
|
}
|
||||||
|
|
||||||
|
send(data) {
|
||||||
|
if (this.ws.readyState === 1) { // WebSocket.OPEN
|
||||||
|
// Providers send raw objects, we stringify for WebSocket
|
||||||
|
this.ws.send(JSON.stringify(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setSessionId(sessionId) {
|
||||||
|
this.sessionId = sessionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
getSessionId() {
|
||||||
|
return this.sessionId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Handle chat WebSocket connections
|
// Handle chat WebSocket connections
|
||||||
function handleChatConnection(ws) {
|
function handleChatConnection(ws) {
|
||||||
console.log('[INFO] Chat WebSocket connected');
|
console.log('[INFO] Chat WebSocket connected');
|
||||||
@@ -724,6 +750,9 @@ function handleChatConnection(ws) {
|
|||||||
// Add to connected clients for project updates
|
// Add to connected clients for project updates
|
||||||
connectedClients.add(ws);
|
connectedClients.add(ws);
|
||||||
|
|
||||||
|
// Wrap WebSocket with writer for consistent interface with SSEStreamWriter
|
||||||
|
const writer = new WebSocketWriter(ws);
|
||||||
|
|
||||||
ws.on('message', async (message) => {
|
ws.on('message', async (message) => {
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(message);
|
const data = JSON.parse(message);
|
||||||
@@ -734,19 +763,19 @@ function handleChatConnection(ws) {
|
|||||||
console.log('🔄 Session:', data.options?.sessionId ? 'Resume' : 'New');
|
console.log('🔄 Session:', data.options?.sessionId ? 'Resume' : 'New');
|
||||||
|
|
||||||
// Use Claude Agents SDK
|
// Use Claude Agents SDK
|
||||||
await queryClaudeSDK(data.command, data.options, ws);
|
await queryClaudeSDK(data.command, data.options, writer);
|
||||||
} else if (data.type === 'cursor-command') {
|
} else if (data.type === 'cursor-command') {
|
||||||
console.log('[DEBUG] Cursor message:', data.command || '[Continue/Resume]');
|
console.log('[DEBUG] Cursor message:', data.command || '[Continue/Resume]');
|
||||||
console.log('📁 Project:', data.options?.cwd || 'Unknown');
|
console.log('📁 Project:', data.options?.cwd || 'Unknown');
|
||||||
console.log('🔄 Session:', data.options?.sessionId ? 'Resume' : 'New');
|
console.log('🔄 Session:', data.options?.sessionId ? 'Resume' : 'New');
|
||||||
console.log('🤖 Model:', data.options?.model || 'default');
|
console.log('🤖 Model:', data.options?.model || 'default');
|
||||||
await spawnCursor(data.command, data.options, ws);
|
await spawnCursor(data.command, data.options, writer);
|
||||||
} else if (data.type === 'codex-command') {
|
} else if (data.type === 'codex-command') {
|
||||||
console.log('[DEBUG] Codex message:', data.command || '[Continue/Resume]');
|
console.log('[DEBUG] Codex message:', data.command || '[Continue/Resume]');
|
||||||
console.log('📁 Project:', data.options?.projectPath || data.options?.cwd || 'Unknown');
|
console.log('📁 Project:', data.options?.projectPath || data.options?.cwd || 'Unknown');
|
||||||
console.log('🔄 Session:', data.options?.sessionId ? 'Resume' : 'New');
|
console.log('🔄 Session:', data.options?.sessionId ? 'Resume' : 'New');
|
||||||
console.log('🤖 Model:', data.options?.model || 'default');
|
console.log('🤖 Model:', data.options?.model || 'default');
|
||||||
await queryCodex(data.command, data.options, ws);
|
await queryCodex(data.command, data.options, writer);
|
||||||
} else if (data.type === 'cursor-resume') {
|
} else if (data.type === 'cursor-resume') {
|
||||||
// Backward compatibility: treat as cursor-command with resume and no prompt
|
// Backward compatibility: treat as cursor-command with resume and no prompt
|
||||||
console.log('[DEBUG] Cursor resume session (compat):', data.sessionId);
|
console.log('[DEBUG] Cursor resume session (compat):', data.sessionId);
|
||||||
@@ -754,7 +783,7 @@ function handleChatConnection(ws) {
|
|||||||
sessionId: data.sessionId,
|
sessionId: data.sessionId,
|
||||||
resume: true,
|
resume: true,
|
||||||
cwd: data.options?.cwd
|
cwd: data.options?.cwd
|
||||||
}, ws);
|
}, writer);
|
||||||
} else if (data.type === 'abort-session') {
|
} else if (data.type === 'abort-session') {
|
||||||
console.log('[DEBUG] Abort session request:', data.sessionId);
|
console.log('[DEBUG] Abort session request:', data.sessionId);
|
||||||
const provider = data.provider || 'claude';
|
const provider = data.provider || 'claude';
|
||||||
@@ -769,21 +798,21 @@ function handleChatConnection(ws) {
|
|||||||
success = await abortClaudeSDKSession(data.sessionId);
|
success = await abortClaudeSDKSession(data.sessionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
ws.send(JSON.stringify({
|
writer.send({
|
||||||
type: 'session-aborted',
|
type: 'session-aborted',
|
||||||
sessionId: data.sessionId,
|
sessionId: data.sessionId,
|
||||||
provider,
|
provider,
|
||||||
success
|
success
|
||||||
}));
|
});
|
||||||
} else if (data.type === 'cursor-abort') {
|
} else if (data.type === 'cursor-abort') {
|
||||||
console.log('[DEBUG] Abort Cursor session:', data.sessionId);
|
console.log('[DEBUG] Abort Cursor session:', data.sessionId);
|
||||||
const success = abortCursorSession(data.sessionId);
|
const success = abortCursorSession(data.sessionId);
|
||||||
ws.send(JSON.stringify({
|
writer.send({
|
||||||
type: 'session-aborted',
|
type: 'session-aborted',
|
||||||
sessionId: data.sessionId,
|
sessionId: data.sessionId,
|
||||||
provider: 'cursor',
|
provider: 'cursor',
|
||||||
success
|
success
|
||||||
}));
|
});
|
||||||
} else if (data.type === 'check-session-status') {
|
} else if (data.type === 'check-session-status') {
|
||||||
// Check if a specific session is currently processing
|
// Check if a specific session is currently processing
|
||||||
const provider = data.provider || 'claude';
|
const provider = data.provider || 'claude';
|
||||||
@@ -799,12 +828,12 @@ function handleChatConnection(ws) {
|
|||||||
isActive = isClaudeSDKSessionActive(sessionId);
|
isActive = isClaudeSDKSessionActive(sessionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
ws.send(JSON.stringify({
|
writer.send({
|
||||||
type: 'session-status',
|
type: 'session-status',
|
||||||
sessionId,
|
sessionId,
|
||||||
provider,
|
provider,
|
||||||
isProcessing: isActive
|
isProcessing: isActive
|
||||||
}));
|
});
|
||||||
} else if (data.type === 'get-active-sessions') {
|
} else if (data.type === 'get-active-sessions') {
|
||||||
// Get all currently active sessions
|
// Get all currently active sessions
|
||||||
const activeSessions = {
|
const activeSessions = {
|
||||||
@@ -812,17 +841,17 @@ function handleChatConnection(ws) {
|
|||||||
cursor: getActiveCursorSessions(),
|
cursor: getActiveCursorSessions(),
|
||||||
codex: getActiveCodexSessions()
|
codex: getActiveCodexSessions()
|
||||||
};
|
};
|
||||||
ws.send(JSON.stringify({
|
writer.send({
|
||||||
type: 'active-sessions',
|
type: 'active-sessions',
|
||||||
sessions: activeSessions
|
sessions: activeSessions
|
||||||
}));
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[ERROR] Chat WebSocket error:', error.message);
|
console.error('[ERROR] Chat WebSocket error:', error.message);
|
||||||
ws.send(JSON.stringify({
|
writer.send({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
error: error.message
|
error: error.message
|
||||||
}));
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -213,7 +213,8 @@ export async function queryCodex(command, options = {}, ws) {
|
|||||||
workingDirectory,
|
workingDirectory,
|
||||||
skipGitRepoCheck: true,
|
skipGitRepoCheck: true,
|
||||||
sandboxMode,
|
sandboxMode,
|
||||||
approvalPolicy
|
approvalPolicy,
|
||||||
|
model
|
||||||
};
|
};
|
||||||
|
|
||||||
// Start or resume thread
|
// Start or resume thread
|
||||||
@@ -359,12 +360,12 @@ export function getActiveCodexSessions() {
|
|||||||
*/
|
*/
|
||||||
function sendMessage(ws, data) {
|
function sendMessage(ws, data) {
|
||||||
try {
|
try {
|
||||||
if (typeof ws.send === 'function') {
|
if (ws.isSSEStreamWriter || ws.isWebSocketWriter) {
|
||||||
// WebSocket
|
// Writer handles stringification (SSEStreamWriter or WebSocketWriter)
|
||||||
|
ws.send(data);
|
||||||
|
} else if (typeof ws.send === 'function') {
|
||||||
|
// Raw WebSocket - stringify here
|
||||||
ws.send(JSON.stringify(data));
|
ws.send(JSON.stringify(data));
|
||||||
} else if (typeof ws.write === 'function') {
|
|
||||||
// SSE writer (for agent API)
|
|
||||||
ws.write(`data: ${JSON.stringify(data)}\n\n`);
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Codex] Error sending message:', error);
|
console.error('[Codex] Error sending message:', error);
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { queryClaudeSDK } from '../claude-sdk.js';
|
|||||||
import { spawnCursor } from '../cursor-cli.js';
|
import { spawnCursor } from '../cursor-cli.js';
|
||||||
import { queryCodex } from '../openai-codex.js';
|
import { queryCodex } from '../openai-codex.js';
|
||||||
import { Octokit } from '@octokit/rest';
|
import { Octokit } from '@octokit/rest';
|
||||||
|
import { CLAUDE_MODELS, CURSOR_MODELS, CODEX_MODELS } from '../../shared/modelConstants.js';
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
@@ -450,6 +451,7 @@ class SSEStreamWriter {
|
|||||||
constructor(res) {
|
constructor(res) {
|
||||||
this.res = res;
|
this.res = res;
|
||||||
this.sessionId = null;
|
this.sessionId = null;
|
||||||
|
this.isSSEStreamWriter = true; // Marker for transport detection
|
||||||
}
|
}
|
||||||
|
|
||||||
send(data) {
|
send(data) {
|
||||||
@@ -457,7 +459,7 @@ class SSEStreamWriter {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format as SSE
|
// Format as SSE - providers send raw objects, we stringify
|
||||||
this.res.write(`data: ${JSON.stringify(data)}\n\n`);
|
this.res.write(`data: ${JSON.stringify(data)}\n\n`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -634,9 +636,14 @@ class ResponseCollector {
|
|||||||
* - true: Returns text/event-stream with incremental updates
|
* - true: Returns text/event-stream with incremental updates
|
||||||
* - false: Returns complete JSON response after completion
|
* - false: Returns complete JSON response after completion
|
||||||
*
|
*
|
||||||
* @param {string} model - (Optional) Model identifier for Cursor provider.
|
* @param {string} model - (Optional) Model identifier for providers.
|
||||||
* Only applicable when provider='cursor'.
|
*
|
||||||
* Examples: 'gpt-4', 'claude-3-opus', etc.
|
* Claude models: 'sonnet' (default), 'opus', 'haiku', 'opusplan', 'sonnet[1m]'
|
||||||
|
* Cursor models: 'gpt-5' (default), 'gpt-5.2', 'gpt-5.2-high', 'sonnet-4.5', 'opus-4.5',
|
||||||
|
* 'gemini-3-pro', 'composer-1', 'auto', 'gpt-5.1', 'gpt-5.1-high',
|
||||||
|
* 'gpt-5.1-codex', 'gpt-5.1-codex-high', 'gpt-5.1-codex-max',
|
||||||
|
* 'gpt-5.1-codex-max-high', 'opus-4.1', 'grok', and thinking variants
|
||||||
|
* Codex models: 'gpt-5.2' (default), 'gpt-5.1-codex-max', 'o3', 'o4-mini'
|
||||||
*
|
*
|
||||||
* @param {boolean} cleanup - (Optional) Auto-cleanup project directory after completion.
|
* @param {boolean} cleanup - (Optional) Auto-cleanup project directory after completion.
|
||||||
* Default: true
|
* Default: true
|
||||||
@@ -939,6 +946,7 @@ router.post('/', validateExternalApiKey, async (req, res) => {
|
|||||||
projectPath: finalProjectPath,
|
projectPath: finalProjectPath,
|
||||||
cwd: finalProjectPath,
|
cwd: finalProjectPath,
|
||||||
sessionId: null, // New session
|
sessionId: null, // New session
|
||||||
|
model: model,
|
||||||
permissionMode: 'bypassPermissions' // Bypass all permissions for API calls
|
permissionMode: 'bypassPermissions' // Bypass all permissions for API calls
|
||||||
}, writer);
|
}, writer);
|
||||||
|
|
||||||
@@ -959,7 +967,7 @@ router.post('/', validateExternalApiKey, async (req, res) => {
|
|||||||
projectPath: finalProjectPath,
|
projectPath: finalProjectPath,
|
||||||
cwd: finalProjectPath,
|
cwd: finalProjectPath,
|
||||||
sessionId: null,
|
sessionId: null,
|
||||||
model: model || 'gpt-5.2',
|
model: model || CODEX_MODELS.DEFAULT,
|
||||||
permissionMode: 'bypassPermissions'
|
permissionMode: 'bypassPermissions'
|
||||||
}, writer);
|
}, writer);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import path from 'path';
|
|||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import matter from 'gray-matter';
|
import matter from 'gray-matter';
|
||||||
|
import { CLAUDE_MODELS, CURSOR_MODELS, CODEX_MODELS } from '../../shared/modelConstants.js';
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
@@ -182,23 +183,15 @@ Custom commands can be created in:
|
|||||||
},
|
},
|
||||||
|
|
||||||
'/model': async (args, context) => {
|
'/model': async (args, context) => {
|
||||||
// Read available models from config or defaults
|
// Read available models from centralized constants
|
||||||
const availableModels = {
|
const availableModels = {
|
||||||
claude: [
|
claude: CLAUDE_MODELS.OPTIONS.map(o => o.value),
|
||||||
'claude-sonnet-4.5',
|
cursor: CURSOR_MODELS.OPTIONS.map(o => o.value),
|
||||||
'claude-sonnet-4',
|
codex: CODEX_MODELS.OPTIONS.map(o => o.value)
|
||||||
'claude-opus-4',
|
|
||||||
'claude-sonnet-3.5'
|
|
||||||
],
|
|
||||||
cursor: [
|
|
||||||
'gpt-5',
|
|
||||||
'sonnet-4',
|
|
||||||
'opus-4.1'
|
|
||||||
]
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const currentProvider = context?.provider || 'claude';
|
const currentProvider = context?.provider || 'claude';
|
||||||
const currentModel = context?.model || 'claude-sonnet-4.5';
|
const currentModel = context?.model || CLAUDE_MODELS.DEFAULT;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'builtin',
|
type: 'builtin',
|
||||||
@@ -216,50 +209,6 @@ Custom commands can be created in:
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
'/cost': async (args, context) => {
|
|
||||||
// Calculate token usage and cost
|
|
||||||
const sessionId = context?.sessionId;
|
|
||||||
const tokenUsage = context?.tokenUsage || { used: 0, total: 200000 };
|
|
||||||
|
|
||||||
const costPerMillion = {
|
|
||||||
'claude-sonnet-4.5': { input: 3, output: 15 },
|
|
||||||
'claude-sonnet-4': { input: 3, output: 15 },
|
|
||||||
'claude-opus-4': { input: 15, output: 75 },
|
|
||||||
'gpt-5': { input: 5, output: 15 }
|
|
||||||
};
|
|
||||||
|
|
||||||
const model = context?.model || 'claude-sonnet-4.5';
|
|
||||||
const rates = costPerMillion[model] || costPerMillion['claude-sonnet-4.5'];
|
|
||||||
|
|
||||||
// Estimate 70% input, 30% output
|
|
||||||
const estimatedInputTokens = Math.floor(tokenUsage.used * 0.7);
|
|
||||||
const estimatedOutputTokens = Math.floor(tokenUsage.used * 0.3);
|
|
||||||
|
|
||||||
const inputCost = (estimatedInputTokens / 1000000) * rates.input;
|
|
||||||
const outputCost = (estimatedOutputTokens / 1000000) * rates.output;
|
|
||||||
const totalCost = inputCost + outputCost;
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: 'builtin',
|
|
||||||
action: 'cost',
|
|
||||||
data: {
|
|
||||||
tokenUsage: {
|
|
||||||
used: tokenUsage.used,
|
|
||||||
total: tokenUsage.total,
|
|
||||||
percentage: ((tokenUsage.used / tokenUsage.total) * 100).toFixed(1)
|
|
||||||
},
|
|
||||||
cost: {
|
|
||||||
input: inputCost.toFixed(4),
|
|
||||||
output: outputCost.toFixed(4),
|
|
||||||
total: totalCost.toFixed(4),
|
|
||||||
currency: 'USD'
|
|
||||||
},
|
|
||||||
model,
|
|
||||||
rates
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
'/status': async (args, context) => {
|
'/status': async (args, context) => {
|
||||||
// Read version from package.json
|
// Read version from package.json
|
||||||
const packageJsonPath = path.join(path.dirname(__dirname), '..', 'package.json');
|
const packageJsonPath = path.join(path.dirname(__dirname), '..', 'package.json');
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { spawn } from 'child_process';
|
|||||||
import sqlite3 from 'sqlite3';
|
import sqlite3 from 'sqlite3';
|
||||||
import { open } from 'sqlite';
|
import { open } from 'sqlite';
|
||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
|
import { CURSOR_MODELS } from '../../shared/modelConstants.js';
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
@@ -33,7 +34,7 @@ router.get('/config', async (req, res) => {
|
|||||||
config: {
|
config: {
|
||||||
version: 1,
|
version: 1,
|
||||||
model: {
|
model: {
|
||||||
modelId: "gpt-5",
|
modelId: CURSOR_MODELS.DEFAULT,
|
||||||
displayName: "GPT-5"
|
displayName: "GPT-5"
|
||||||
},
|
},
|
||||||
permissions: {
|
permissions: {
|
||||||
|
|||||||
65
shared/modelConstants.js
Normal file
65
shared/modelConstants.js
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
/**
|
||||||
|
* Centralized Model Definitions
|
||||||
|
* Single source of truth for all supported AI models
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Claude (Anthropic) Models
|
||||||
|
*
|
||||||
|
* Note: Claude uses two different formats:
|
||||||
|
* - SDK format ('sonnet', 'opus') - used by the UI and claude-sdk.js
|
||||||
|
* - API format ('claude-sonnet-4.5') - used by slash commands for display
|
||||||
|
*/
|
||||||
|
export const CLAUDE_MODELS = {
|
||||||
|
// Models in SDK format (what the actual SDK accepts)
|
||||||
|
OPTIONS: [
|
||||||
|
{ value: 'sonnet', label: 'Sonnet' },
|
||||||
|
{ value: 'opus', label: 'Opus' },
|
||||||
|
{ value: 'haiku', label: 'Haiku' },
|
||||||
|
{ value: 'opusplan', label: 'Opus Plan' },
|
||||||
|
{ value: 'sonnet[1m]', label: 'Sonnet [1M]' }
|
||||||
|
],
|
||||||
|
|
||||||
|
DEFAULT: 'sonnet'
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cursor Models
|
||||||
|
*/
|
||||||
|
export const CURSOR_MODELS = {
|
||||||
|
OPTIONS: [
|
||||||
|
{ value: 'gpt-5.2-high', label: 'GPT-5.2 High' },
|
||||||
|
{ value: 'gemini-3-pro', label: 'Gemini 3 Pro' },
|
||||||
|
{ value: 'opus-4.5-thinking', label: 'Claude 4.5 Opus (Thinking)' },
|
||||||
|
{ value: 'gpt-5.2', label: 'GPT-5.2' },
|
||||||
|
{ value: 'gpt-5.1', label: 'GPT-5.1' },
|
||||||
|
{ value: 'gpt-5.1-high', label: 'GPT-5.1 High' },
|
||||||
|
{ value: 'composer-1', label: 'Composer 1' },
|
||||||
|
{ value: 'auto', label: 'Auto' },
|
||||||
|
{ value: 'sonnet-4.5', label: 'Claude 4.5 Sonnet' },
|
||||||
|
{ value: 'sonnet-4.5-thinking', label: 'Claude 4.5 Sonnet (Thinking)' },
|
||||||
|
{ value: 'opus-4.5', label: 'Claude 4.5 Opus' },
|
||||||
|
{ value: 'gpt-5.1-codex', label: 'GPT-5.1 Codex' },
|
||||||
|
{ value: 'gpt-5.1-codex-high', label: 'GPT-5.1 Codex High' },
|
||||||
|
{ value: 'gpt-5.1-codex-max', label: 'GPT-5.1 Codex Max' },
|
||||||
|
{ value: 'gpt-5.1-codex-max-high', label: 'GPT-5.1 Codex Max High' },
|
||||||
|
{ value: 'opus-4.1', label: 'Claude 4.1 Opus' },
|
||||||
|
{ value: 'grok', label: 'Grok' }
|
||||||
|
],
|
||||||
|
|
||||||
|
DEFAULT: 'gpt-5'
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Codex (OpenAI) Models
|
||||||
|
*/
|
||||||
|
export const CODEX_MODELS = {
|
||||||
|
OPTIONS: [
|
||||||
|
{ value: 'gpt-5.2', label: 'GPT-5.2' },
|
||||||
|
{ value: 'gpt-5.1-codex-max', label: 'GPT-5.1 Codex Max' },
|
||||||
|
{ value: 'o3', label: 'O3' },
|
||||||
|
{ value: 'o4-mini', label: 'O4-mini' }
|
||||||
|
],
|
||||||
|
|
||||||
|
DEFAULT: 'gpt-5.2'
|
||||||
|
};
|
||||||
@@ -35,6 +35,7 @@ import { MicButton } from './MicButton.jsx';
|
|||||||
import { api, authenticatedFetch } from '../utils/api';
|
import { api, authenticatedFetch } from '../utils/api';
|
||||||
import Fuse from 'fuse.js';
|
import Fuse from 'fuse.js';
|
||||||
import CommandMenu from './CommandMenu';
|
import CommandMenu from './CommandMenu';
|
||||||
|
import { CLAUDE_MODELS, CURSOR_MODELS, CODEX_MODELS } from '../../shared/modelConstants';
|
||||||
|
|
||||||
|
|
||||||
// Helper function to decode HTML entities in text
|
// Helper function to decode HTML entities in text
|
||||||
@@ -1723,13 +1724,13 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
|
|||||||
return localStorage.getItem('selected-provider') || 'claude';
|
return localStorage.getItem('selected-provider') || 'claude';
|
||||||
});
|
});
|
||||||
const [cursorModel, setCursorModel] = useState(() => {
|
const [cursorModel, setCursorModel] = useState(() => {
|
||||||
return localStorage.getItem('cursor-model') || 'gpt-5';
|
return localStorage.getItem('cursor-model') || CURSOR_MODELS.DEFAULT;
|
||||||
});
|
});
|
||||||
const [claudeModel, setClaudeModel] = useState(() => {
|
const [claudeModel, setClaudeModel] = useState(() => {
|
||||||
return localStorage.getItem('claude-model') || 'sonnet';
|
return localStorage.getItem('claude-model') || CLAUDE_MODELS.DEFAULT;
|
||||||
});
|
});
|
||||||
const [codexModel, setCodexModel] = useState(() => {
|
const [codexModel, setCodexModel] = useState(() => {
|
||||||
return localStorage.getItem('codex-model') || 'gpt-5.2';
|
return localStorage.getItem('codex-model') || CODEX_MODELS.DEFAULT;
|
||||||
});
|
});
|
||||||
// Load permission mode for the current session
|
// Load permission mode for the current session
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -1758,17 +1759,10 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
|
|||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (data.success && data.config?.model?.modelId) {
|
if (data.success && data.config?.model?.modelId) {
|
||||||
// Map Cursor model IDs to our simplified names
|
// Use the model from config directly
|
||||||
const modelMap = {
|
const modelId = data.config.model.modelId;
|
||||||
'gpt-5': 'gpt-5',
|
|
||||||
'claude-4-sonnet': 'sonnet-4',
|
|
||||||
'sonnet-4': 'sonnet-4',
|
|
||||||
'claude-4-opus': 'opus-4.1',
|
|
||||||
'opus-4.1': 'opus-4.1'
|
|
||||||
};
|
|
||||||
const mappedModel = modelMap[data.config.model.modelId] || data.config.model.modelId;
|
|
||||||
if (!localStorage.getItem('cursor-model')) {
|
if (!localStorage.getItem('cursor-model')) {
|
||||||
setCursorModel(mappedModel);
|
setCursorModel(modelId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -4547,11 +4541,9 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
|
|||||||
}}
|
}}
|
||||||
className="pl-4 pr-10 py-2 text-sm bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500 min-w-[140px]"
|
className="pl-4 pr-10 py-2 text-sm bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500 min-w-[140px]"
|
||||||
>
|
>
|
||||||
<option value="sonnet">Sonnet</option>
|
{CLAUDE_MODELS.OPTIONS.map(({ value, label }) => (
|
||||||
<option value="opus">Opus</option>
|
<option key={value} value={value}>{label}</option>
|
||||||
<option value="haiku">Haiku</option>
|
))}
|
||||||
<option value="opusplan">Opus Plan</option>
|
|
||||||
<option value="sonnet[1m]">Sonnet [1M]</option>
|
|
||||||
</select>
|
</select>
|
||||||
) : provider === 'codex' ? (
|
) : provider === 'codex' ? (
|
||||||
<select
|
<select
|
||||||
@@ -4563,10 +4555,9 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
|
|||||||
}}
|
}}
|
||||||
className="pl-4 pr-10 py-2 text-sm bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-gray-500 focus:border-gray-500 min-w-[140px]"
|
className="pl-4 pr-10 py-2 text-sm bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-gray-500 focus:border-gray-500 min-w-[140px]"
|
||||||
>
|
>
|
||||||
<option value="gpt-5.2">GPT-5.2</option>
|
{CODEX_MODELS.OPTIONS.map(({ value, label }) => (
|
||||||
<option value="gpt-5.1-codex-max">GPT-5.1 Codex Max</option>
|
<option key={value} value={value}>{label}</option>
|
||||||
<option value="o3">O3</option>
|
))}
|
||||||
<option value="o4-mini">O4-mini</option>
|
|
||||||
</select>
|
</select>
|
||||||
) : (
|
) : (
|
||||||
<select
|
<select
|
||||||
@@ -4579,23 +4570,9 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
|
|||||||
className="pl-4 pr-10 py-2 text-sm bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500 min-w-[140px]"
|
className="pl-4 pr-10 py-2 text-sm bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500 min-w-[140px]"
|
||||||
disabled={provider !== 'cursor'}
|
disabled={provider !== 'cursor'}
|
||||||
>
|
>
|
||||||
<option value="gpt-5.2-high">GPT-5.2 High</option>
|
{CURSOR_MODELS.OPTIONS.map(({ value, label }) => (
|
||||||
<option value="gemini-3-pro">Gemini 3 Pro</option>
|
<option key={value} value={value}>{label}</option>
|
||||||
<option value="opus-4.5-thinking">Claude 4.5 Opus (Thinking)</option>
|
))}
|
||||||
<option value="gpt-5.2">GPT-5.2</option>
|
|
||||||
<option value="gpt-5.1">GPT-5.1</option>
|
|
||||||
<option value="gpt-5.1-high">GPT-5.1 High</option>
|
|
||||||
<option value="composer-1">Composer 1</option>
|
|
||||||
<option value="auto">Auto</option>
|
|
||||||
<option value="sonnet-4.5">Claude 4.5 Sonnet</option>
|
|
||||||
<option value="sonnet-4.5-thinking">Claude 4.5 Sonnet (Thinking)</option>
|
|
||||||
<option value="opus-4.5">Claude 4.5 Opus</option>
|
|
||||||
<option value="gpt-5.1-codex">GPT-5.1 Codex</option>
|
|
||||||
<option value="gpt-5.1-codex-high">GPT-5.1 Codex High</option>
|
|
||||||
<option value="gpt-5.1-codex-max">GPT-5.1 Codex Max</option>
|
|
||||||
<option value="gpt-5.1-codex-max-high">GPT-5.1 Codex Max High</option>
|
|
||||||
<option value="opus-4.1">Claude 4.1 Opus</option>
|
|
||||||
<option value="grok">Grok</option>
|
|
||||||
</select>
|
</select>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user