feat(daemon): enhance conversation session mode handling

- Added a new `mode` flag to CLI commands for project and conversation creation, allowing users to specify 'design' or 'chat' modes.
- Implemented `normalizeChatSessionModeFlag` function to validate and normalize session mode inputs.
- Updated project routes to handle session mode during conversation creation and updates.
- Enhanced web components to support session mode changes, including new props and handlers for managing session modes in conversations.
- Adjusted UI elements to reflect the current session mode, improving user experience and interaction flexibility.

This update provides a more robust framework for managing conversation modes, catering to diverse user needs and enhancing overall functionality.
This commit is contained in:
pftom 2026-05-31 17:11:03 +08:00
parent 79c039efdf
commit 44492af1fa
9 changed files with 102 additions and 16 deletions

View file

@ -152,7 +152,7 @@ const PROJECT_STRING_FLAGS = new Set([
'daemon-url', 'name', 'skill', 'design-system', 'plugin', 'metadata-json', 'daemon-url', 'name', 'skill', 'design-system', 'plugin', 'metadata-json',
'pending-prompt', 'project', 'conversation', 'message', 'path', 'as', 'pending-prompt', 'project', 'conversation', 'message', 'path', 'as',
'agent', 'model', 'snapshot-id', 'inputs', 'grant-caps', 'editor', 'agent', 'model', 'snapshot-id', 'inputs', 'grant-caps', 'editor',
'title', 'against', 'seed-from', 'title', 'against', 'seed-from', 'mode',
]); ]);
const PROJECT_BOOLEAN_FLAGS = new Set(['help', 'h', 'json', 'follow']); const PROJECT_BOOLEAN_FLAGS = new Set(['help', 'h', 'json', 'follow']);
// `od automation …` mirrors the Automations tab. Same surface, same // `od automation …` mirrors the Automations tab. Same surface, same
@ -4319,6 +4319,14 @@ async function projectDaemonUrl(flags) {
return cliDaemonUrl(flags); return cliDaemonUrl(flags);
} }
function normalizeChatSessionModeFlag(value) {
if (value == null) return undefined;
const mode = String(value).trim().toLowerCase();
if (mode === 'design' || mode === 'chat') return mode;
console.error('--mode must be one of: design, chat');
process.exit(2);
}
function safeReadJsonFile(p) { function safeReadJsonFile(p) {
try { try {
const fs = (require ? require('node:fs') : null); const fs = (require ? require('node:fs') : null);
@ -4335,6 +4343,7 @@ async function runProject(args) {
console.log(`Usage: console.log(`Usage:
od project create [--name "<title>"] [--skill <id>] [--design-system <id>] od project create [--name "<title>"] [--skill <id>] [--design-system <id>]
[--plugin <id>] [--inputs <json>] [--metadata-json <path|->] [--plugin <id>] [--inputs <json>] [--metadata-json <path|->]
[--mode design|chat]
od project import <baseDir> [--name "<title>"] od project import <baseDir> [--name "<title>"]
od project list List projects. od project list List projects.
od project info <id> Print one project. od project info <id> Print one project.
@ -4406,6 +4415,8 @@ Common options:
skillId: flags.skill ?? null, skillId: flags.skill ?? null,
designSystemId: flags['design-system'] ?? null, designSystemId: flags['design-system'] ?? null,
}; };
const conversationMode = normalizeChatSessionModeFlag(flags.mode);
if (conversationMode) body.conversationMode = conversationMode;
if (flags['pending-prompt']) body.pendingPrompt = flags['pending-prompt']; if (flags['pending-prompt']) body.pendingPrompt = flags['pending-prompt'];
if (flags['metadata-json']) { if (flags['metadata-json']) {
const mj = safeReadJsonFile(flags['metadata-json']); const mj = safeReadJsonFile(flags['metadata-json']);
@ -5103,7 +5114,7 @@ function renderDiffLineContent(value) {
async function runConversation(args) { async function runConversation(args) {
if (args.length === 0 || args[0] === 'help' || args.includes('--help') || args.includes('-h')) { if (args.length === 0 || args[0] === 'help' || args.includes('--help') || args.includes('-h')) {
console.log(`Usage: console.log(`Usage:
od conversation new <projectId> [--title "<title>"] [--seed-from <cid>] od conversation new <projectId> [--title "<title>"] [--seed-from <cid>] [--mode design|chat]
Create a conversation in a project. Create a conversation in a project.
--seed-from copies another --seed-from copies another
conversation's messages in (Side Chat). conversation's messages in (Side Chat).
@ -5128,6 +5139,8 @@ Common options:
} }
const body = {}; const body = {};
if (typeof flags.title === 'string') body.title = flags.title; if (typeof flags.title === 'string') body.title = flags.title;
const sessionMode = normalizeChatSessionModeFlag(flags.mode);
if (sessionMode) body.sessionMode = sessionMode;
if (typeof flags['seed-from'] === 'string' && flags['seed-from']) { if (typeof flags['seed-from'] === 'string' && flags['seed-from']) {
body.seedFromConversationId = flags['seed-from']; body.seedFromConversationId = flags['seed-from'];
} }
@ -5139,7 +5152,8 @@ Common options:
if (!resp.ok) return structuredHttpFailure(resp, 'project-not-found'); if (!resp.ok) return structuredHttpFailure(resp, 'project-not-found');
const data = await resp.json(); const data = await resp.json();
if (flags.json) return process.stdout.write(JSON.stringify(data, null, 2) + '\n'); if (flags.json) return process.stdout.write(JSON.stringify(data, null, 2) + '\n');
console.log(`[conversation] created ${data.conversation?.id ?? '-'}`); const conv = data.conversation;
console.log(`[conversation] created ${conv?.id ?? '-'} (mode ${conv?.sessionMode ?? sessionMode ?? 'design'})`);
return; return;
} }
case 'list': { case 'list': {
@ -5185,7 +5199,7 @@ Common options:
async function runChat(args) { async function runChat(args) {
if (args.length === 0 || args[0] === 'help' || args.includes('--help') || args.includes('-h')) { if (args.length === 0 || args[0] === 'help' || args.includes('--help') || args.includes('-h')) {
console.log(`Usage: console.log(`Usage:
od chat new --project <id> [--seed-from <cid>] [--title "<title>"] [--json] od chat new --project <id> [--seed-from <cid>] [--title "<title>"] [--mode design|chat] [--json]
Create a Side Chat a new conversation Create a Side Chat a new conversation
that copies in another conversation's that copies in another conversation's
context (--seed-from) so the new chat context (--seed-from) so the new chat
@ -5213,6 +5227,8 @@ Common options:
} }
const body = {}; const body = {};
if (typeof flags.title === 'string') body.title = flags.title; if (typeof flags.title === 'string') body.title = flags.title;
const sessionMode = normalizeChatSessionModeFlag(flags.mode);
if (sessionMode) body.sessionMode = sessionMode;
if (typeof flags['seed-from'] === 'string' && flags['seed-from']) { if (typeof flags['seed-from'] === 'string' && flags['seed-from']) {
body.seedFromConversationId = flags['seed-from']; body.seedFromConversationId = flags['seed-from'];
} }
@ -5228,7 +5244,7 @@ Common options:
const seeded = body.seedFromConversationId const seeded = body.seedFromConversationId
? ` (seeded from ${body.seedFromConversationId})` ? ` (seeded from ${body.seedFromConversationId})`
: ''; : '';
console.log(`[chat] created ${conv?.id ?? '-'}${conv?.title ? ` "${conv.title}"` : ''}${seeded}`); console.log(`[chat] created ${conv?.id ?? '-'}${conv?.title ? ` "${conv.title}"` : ''}${seeded} (mode ${conv?.sessionMode ?? sessionMode ?? 'design'})`);
return; return;
} }
default: default:

View file

@ -323,13 +323,14 @@ export function registerProjectRoutes(app: Express, ctx: RegisterProjectRoutesDe
}); });
// Seed a default conversation so the UI always has somewhere to write. // Seed a default conversation so the UI always has somewhere to write.
const cid = randomId(); const cid = randomId();
const initialSessionMode = normalizeChatSessionMode(
req.body?.conversationMode ?? req.body?.sessionMode,
);
insertConversation(db, { insertConversation(db, {
id: cid, id: cid,
projectId: id, projectId: id,
title: null, title: null,
sessionMode: normalizeChatSessionMode( sessionMode: initialSessionMode,
req.body?.conversationMode ?? req.body?.sessionMode,
),
createdAt: now, createdAt: now,
updatedAt: now, updatedAt: now,
}); });
@ -341,7 +342,7 @@ export function registerProjectRoutes(app: Express, ctx: RegisterProjectRoutesDe
&& req.body.appliedPluginSnapshotId.trim().length > 0; && req.body.appliedPluginSnapshotId.trim().length > 0;
let resolveBody = let resolveBody =
explicitPlugin ? (req.body as Record<string, unknown>) : null; explicitPlugin ? (req.body as Record<string, unknown>) : null;
if (!resolveBody) { if (!resolveBody && initialSessionMode === 'design') {
const fallbackPluginId = defaultScenarioPluginIdForProjectMetadata(projectMetadata); const fallbackPluginId = defaultScenarioPluginIdForProjectMetadata(projectMetadata);
if (fallbackPluginId && getInstalledPlugin(db, fallbackPluginId)) { if (fallbackPluginId && getInstalledPlugin(db, fallbackPluginId)) {
resolveBody = { ...(req.body || {}), pluginId: fallbackPluginId }; resolveBody = { ...(req.body || {}), pluginId: fallbackPluginId };

View file

@ -6,6 +6,7 @@ import {
type DragEvent as ReactDragEvent, type DragEvent as ReactDragEvent,
} from 'react'; } from 'react';
import type { TrackingProjectKind } from '@open-design/contracts/analytics'; import type { TrackingProjectKind } from '@open-design/contracts/analytics';
import type { ChatSessionMode } from '@open-design/contracts';
import { useAnalytics } from '../analytics/provider'; import { useAnalytics } from '../analytics/provider';
import { import {
trackFileManagerClick, trackFileManagerClick,
@ -136,6 +137,7 @@ interface Props {
onSelectConversation?: (id: string) => void; onSelectConversation?: (id: string) => void;
onDeleteConversation?: (id: string) => void; onDeleteConversation?: (id: string) => void;
onRenameConversation?: (id: string, title: string) => void; onRenameConversation?: (id: string, title: string) => void;
onConversationSessionModeChange?: (id: string, mode: ChatSessionMode) => void;
onNewConversation?: () => void; onNewConversation?: () => void;
/** Create a context-seeded conversation and resolve its id (backs the launcher). */ /** Create a context-seeded conversation and resolve its id (backs the launcher). */
onCreateSideChat?: (seedFromConversationId: string | null) => Promise<string | null>; onCreateSideChat?: (seedFromConversationId: string | null) => Promise<string | null>;
@ -265,6 +267,7 @@ export function FileWorkspace({
onSelectConversation, onSelectConversation,
onDeleteConversation, onDeleteConversation,
onRenameConversation, onRenameConversation,
onConversationSessionModeChange,
onNewConversation, onNewConversation,
onCreateSideChat, onCreateSideChat,
}: Props) { }: Props) {
@ -1246,6 +1249,7 @@ export function FileWorkspace({
onSelectConversation={onSelectConversation ?? (() => {})} onSelectConversation={onSelectConversation ?? (() => {})}
onDeleteConversation={onDeleteConversation ?? (() => {})} onDeleteConversation={onDeleteConversation ?? (() => {})}
onRenameConversation={onRenameConversation} onRenameConversation={onRenameConversation}
onSessionModeChange={onConversationSessionModeChange}
onNewConversation={onNewConversation} onNewConversation={onNewConversation}
onRequestOpenFile={openFile} onRequestOpenFile={openFile}
/> />

View file

@ -107,7 +107,7 @@ import {
type SaveMessageOptions, type SaveMessageOptions,
waitGeneratedPluginShareTask, waitGeneratedPluginShareTask,
} from '../state/projects'; } from '../state/projects';
import type { AppliedPluginSnapshot } from '@open-design/contracts'; import type { AppliedPluginSnapshot, ChatSessionMode } from '@open-design/contracts';
import type { import type {
AgentEvent, AgentEvent,
AgentInfo, AgentInfo,
@ -115,7 +115,6 @@ import type {
Artifact, Artifact,
ChatAttachment, ChatAttachment,
ChatCommentAttachment, ChatCommentAttachment,
ChatSessionMode,
ChatMessage, ChatMessage,
ChatMessageFeedbackChange, ChatMessageFeedbackChange,
Conversation, Conversation,
@ -1434,7 +1433,9 @@ export function ProjectView({
return project.id; return project.id;
}, [project.id]); }, [project.id]);
const composedSystemPrompt = useCallback(async (): Promise<string> => { const composedSystemPrompt = useCallback(async (
sessionModeOverride: ChatSessionMode = activeSessionMode,
): Promise<string> => {
let skillBody: string | undefined; let skillBody: string | undefined;
let skillName: string | undefined; let skillName: string | undefined;
let skillMode: SkillSummary['mode'] | undefined; let skillMode: SkillSummary['mode'] | undefined;
@ -1537,6 +1538,7 @@ export function ProjectView({
audioVoiceOptions, audioVoiceOptions,
audioVoiceOptionsError: audioVoiceOptionsLookupError, audioVoiceOptionsError: audioVoiceOptionsLookupError,
streamFormat: config.mode === 'api' ? 'plain' : undefined, streamFormat: config.mode === 'api' ? 'plain' : undefined,
sessionMode: sessionModeOverride,
locale, locale,
userInstructions: config.customInstructions, userInstructions: config.customInstructions,
projectInstructions: project.customInstructions, projectInstructions: project.customInstructions,
@ -1551,6 +1553,7 @@ export function ProjectView({
designSystems, designSystems,
config.mode, config.mode,
config.customInstructions, config.customInstructions,
activeSessionMode,
locale, locale,
]); ]);
@ -2267,6 +2270,7 @@ export function ProjectView({
) => { ) => {
if (!activeConversationId) return; if (!activeConversationId) return;
if (messagesConversationIdRef.current !== activeConversationId) return; if (messagesConversationIdRef.current !== activeConversationId) return;
const runSessionMode = meta?.sessionMode ?? activeSessionMode;
const retryTarget = meta?.retryOfAssistantId const retryTarget = meta?.retryOfAssistantId
? resolveRetryTarget(messages, meta.retryOfAssistantId) ? resolveRetryTarget(messages, meta.retryOfAssistantId)
: null; : null;
@ -2285,7 +2289,7 @@ export function ProjectView({
prompt, prompt,
attachments, attachments,
commentAttachments, commentAttachments,
...(meta === undefined ? {} : { meta }), meta: { ...(meta ?? {}), sessionMode: runSessionMode },
createdAt: Date.now(), createdAt: Date.now(),
}); });
if (commentAttachments.length > 0) { if (commentAttachments.length > 0) {
@ -2770,6 +2774,7 @@ export function ProjectView({
designSystemId: project.designSystemId ?? null, designSystemId: project.designSystemId ?? null,
attachments: runAttachments.map((a) => a.path), attachments: runAttachments.map((a) => a.path),
commentAttachments: runCommentAttachments, commentAttachments: runCommentAttachments,
sessionMode: runSessionMode,
research: meta?.research, research: meta?.research,
mediaExecution: mediaExecutionPolicyForProjectMetadata(project.metadata), mediaExecution: mediaExecutionPolicyForProjectMetadata(project.metadata),
model: choice?.model ?? null, model: choice?.model ?? null,
@ -2863,7 +2868,7 @@ export function ProjectView({
// on the next event. // on the next event.
} }
} }
const systemPrompt = await composedSystemPrompt(); const systemPrompt = await composedSystemPrompt(runSessionMode);
const apiHistory = await historyWithApiAttachmentContext( const apiHistory = await historyWithApiAttachmentContext(
historyWithCommentAttachmentContext(nextHistory, userMsg.id), historyWithCommentAttachmentContext(nextHistory, userMsg.id),
userMsg.id, userMsg.id,
@ -2911,6 +2916,7 @@ export function ProjectView({
[ [
attachedComments, attachedComments,
activeConversationId, activeConversationId,
activeSessionMode,
currentConversationBusy, currentConversationBusy,
enqueueChatSend, enqueueChatSend,
messages, messages,
@ -3638,6 +3644,33 @@ export function ProjectView({
[project.id], [project.id],
); );
const handleConversationSessionModeChange = useCallback(
async (id: string, sessionMode: ChatSessionMode) => {
setConversations((curr) =>
curr.map((conversation) =>
conversation.id === id ? { ...conversation, sessionMode } : conversation,
),
);
const updated = await patchConversation(project.id, id, { sessionMode });
if (updated) {
setConversations((curr) =>
curr.map((conversation) =>
conversation.id === id ? { ...conversation, ...updated } : conversation,
),
);
}
},
[project.id],
);
const handleActiveConversationSessionModeChange = useCallback(
(sessionMode: ChatSessionMode) => {
if (!activeConversationId) return;
void handleConversationSessionModeChange(activeConversationId, sessionMode);
},
[activeConversationId, handleConversationSessionModeChange],
);
// Side Chat launcher: create a NEW conversation seeded with the current // Side Chat launcher: create a NEW conversation seeded with the current
// chat's context (the daemon copies the source conversation's messages) and // chat's context (the daemon copies the source conversation's messages) and
// resolve its id. The new conversation is a normal conversation, so it shows // resolve its id. The new conversation is a normal conversation, so it shows
@ -4480,6 +4513,8 @@ export function ProjectView({
queuedItems={currentConversationQueuedItems} queuedItems={currentConversationQueuedItems}
error={conversationLoadError ?? error ?? audioVoiceOptionsError} error={conversationLoadError ?? error ?? audioVoiceOptionsError}
projectId={project.id} projectId={project.id}
sessionMode={activeSessionMode}
onSessionModeChange={handleActiveConversationSessionModeChange}
projectKindForTracking={projectKindToTracking(project.metadata?.kind)} projectKindForTracking={projectKindToTracking(project.metadata?.kind)}
projectFiles={projectFiles} projectFiles={projectFiles}
hasActiveDesignSystem={!!project.designSystemId} hasActiveDesignSystem={!!project.designSystemId}
@ -4615,6 +4650,7 @@ export function ProjectView({
onSelectConversation={handleSelectConversation} onSelectConversation={handleSelectConversation}
onDeleteConversation={handleDeleteConversation} onDeleteConversation={handleDeleteConversation}
onRenameConversation={handleRenameConversation} onRenameConversation={handleRenameConversation}
onConversationSessionModeChange={handleConversationSessionModeChange}
onNewConversation={handleNewConversation} onNewConversation={handleNewConversation}
onCreateSideChat={handleCreateSideChat} onCreateSideChat={handleCreateSideChat}
/> />

View file

@ -7,6 +7,7 @@ import type {
Conversation, Conversation,
ProjectFile, ProjectFile,
} from '../../types'; } from '../../types';
import type { ChatSessionMode } from '@open-design/contracts';
import { useConversationChat } from './useConversationChat'; import { useConversationChat } from './useConversationChat';
import styles from './SideChatTab.module.css'; import styles from './SideChatTab.module.css';
@ -28,6 +29,7 @@ interface Props {
onSelectConversation: (id: string) => void; onSelectConversation: (id: string) => void;
onDeleteConversation: (id: string) => void; onDeleteConversation: (id: string) => void;
onRenameConversation?: (id: string, title: string) => void; onRenameConversation?: (id: string, title: string) => void;
onSessionModeChange?: (id: string, mode: ChatSessionMode) => void;
onNewConversation?: () => void; onNewConversation?: () => void;
/** Forward produced-file / tool-card open requests to the workspace. */ /** Forward produced-file / tool-card open requests to the workspace. */
onRequestOpenFile?: (name: string) => void; onRequestOpenFile?: (name: string) => void;
@ -49,14 +51,19 @@ export function SideChatTab({
onSelectConversation, onSelectConversation,
onDeleteConversation, onDeleteConversation,
onRenameConversation, onRenameConversation,
onSessionModeChange,
onNewConversation, onNewConversation,
onRequestOpenFile, onRequestOpenFile,
}: Props) { }: Props) {
const t = useT(); const t = useT();
const sessionMode =
conversations.find((conversation) => conversation.id === conversationId)?.sessionMode
?? 'design';
const chat = useConversationChat(projectId, conversationId, { const chat = useConversationChat(projectId, conversationId, {
config, config,
agentsById, agentsById,
locale, locale,
sessionMode,
}); });
return ( return (
@ -73,6 +80,8 @@ export function SideChatTab({
streaming={chat.streaming} streaming={chat.streaming}
error={chat.error} error={chat.error}
projectId={projectId} projectId={projectId}
sessionMode={sessionMode}
onSessionModeChange={(mode) => onSessionModeChange?.(conversationId, mode)}
projectFiles={projectFiles} projectFiles={projectFiles}
projectFileNames={projectFileNames} projectFileNames={projectFileNames}
onEnsureProject={async () => projectId} onEnsureProject={async () => projectId}

View file

@ -18,6 +18,7 @@ import type {
ChatCommentAttachment, ChatCommentAttachment,
ChatMessage, ChatMessage,
} from '../../types'; } from '../../types';
import type { ChatSessionMode } from '@open-design/contracts';
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// useConversationChat — drives a secondary ChatPane bound to a single // useConversationChat — drives a secondary ChatPane bound to a single
@ -51,6 +52,7 @@ export interface ConversationChatContext {
agentsById: Map<string, AgentInfo>; agentsById: Map<string, AgentInfo>;
/** UI locale forwarded to the daemon so prompts compose in-language. */ /** UI locale forwarded to the daemon so prompts compose in-language. */
locale: string; locale: string;
sessionMode: ChatSessionMode;
} }
export interface UseConversationChatResult { export interface UseConversationChatResult {
@ -137,7 +139,12 @@ export function useConversationChat(
commentAttachments: ChatCommentAttachment[], commentAttachments: ChatCommentAttachment[],
retryOfAssistantId?: string, retryOfAssistantId?: string,
) => { ) => {
const { config: cfg, agentsById: agents, locale: loc } = ctxRef.current; const {
config: cfg,
agentsById: agents,
locale: loc,
sessionMode,
} = ctxRef.current;
if (cfg.mode !== 'daemon') { if (cfg.mode !== 'daemon') {
setError('Side Chat needs a local agent. Pick one in the top bar.'); setError('Side Chat needs a local agent. Pick one in the top bar.');
return; return;
@ -277,6 +284,7 @@ export function useConversationChat(
model: choice?.model ?? null, model: choice?.model ?? null,
reasoning: choice?.reasoning ?? null, reasoning: choice?.reasoning ?? null,
locale: loc, locale: loc,
sessionMode,
onRunCreated: (runId) => { onRunCreated: (runId) => {
updateAssistant(assistantId, (prev) => ({ updateAssistant(assistantId, (prev) => ({
...prev, ...prev,

View file

@ -17,6 +17,7 @@ import type {
ChatRunStatus, ChatRunStatus,
ChatRunStatusResponse, ChatRunStatusResponse,
ChatRequest, ChatRequest,
ChatSessionMode,
ChatSseEvent, ChatSseEvent,
ChatSseStartPayload, ChatSseStartPayload,
DaemonAgentPayload, DaemonAgentPayload,
@ -204,6 +205,7 @@ export interface DaemonStreamOptions {
// workspace. // workspace.
projectId?: string | null; projectId?: string | null;
conversationId?: string | null; conversationId?: string | null;
sessionMode?: ChatSessionMode;
assistantMessageId?: string | null; assistantMessageId?: string | null;
clientRequestId?: string | null; clientRequestId?: string | null;
skillId?: string | null; skillId?: string | null;
@ -300,6 +302,7 @@ export async function streamViaDaemon({
handlers, handlers,
projectId, projectId,
conversationId, conversationId,
sessionMode,
assistantMessageId, assistantMessageId,
clientRequestId, clientRequestId,
skillId, skillId,
@ -333,6 +336,7 @@ export async function streamViaDaemon({
currentPrompt: latestUserPromptFromHistory(history), currentPrompt: latestUserPromptFromHistory(history),
projectId: projectId ?? null, projectId: projectId ?? null,
conversationId: conversationId ?? null, conversationId: conversationId ?? null,
sessionMode,
assistantMessageId: assistantMessageId ?? null, assistantMessageId: assistantMessageId ?? null,
clientRequestId: clientRequestId ?? null, clientRequestId: clientRequestId ?? null,
skillId: skillId ?? null, skillId: skillId ?? null,

View file

@ -629,6 +629,14 @@
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
@media (max-width: 700px) {
.session-mode-toggle__option {
padding: 0 6px;
}
.session-mode-toggle__label {
display: none;
}
}
.composer-import { .composer-import {
background: transparent; background: transparent;
border: 1px solid var(--border); border: 1px solid var(--border);

View file

@ -193,7 +193,7 @@ export interface Conversation {
id: string; id: string;
projectId: string; projectId: string;
title: string | null; title: string | null;
sessionMode: ChatSessionMode; sessionMode?: ChatSessionMode;
createdAt: number; createdAt: number;
updatedAt: number; updatedAt: number;
latestRun?: { latestRun?: {