mirror of
https://github.com/nexu-io/open-design.git
synced 2026-05-31 19:04:39 +07:00
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:
parent
79c039efdf
commit
44492af1fa
9 changed files with 102 additions and 16 deletions
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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 };
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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?: {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue