fix: add goose execution override for h5 chat
This commit is contained in:
+20
-1
@@ -114,8 +114,26 @@ function withAgentRunValidationMetadata(
|
||||
|
||||
function prepareAgentRunUserMessage(message: Message, options: AgentRunCreateOptions): Message {
|
||||
const normalized = normalizeUserMessageForApi(message);
|
||||
const withValidation = withAgentRunValidationMetadata(normalized, options.validation);
|
||||
const withRunMetadata = options.forceGoose
|
||||
? {
|
||||
...withValidation,
|
||||
metadata: {
|
||||
...withValidation.metadata,
|
||||
memindRun: {
|
||||
...(
|
||||
withValidation.metadata?.memindRun &&
|
||||
typeof withValidation.metadata.memindRun === 'object'
|
||||
? withValidation.metadata.memindRun
|
||||
: {}
|
||||
),
|
||||
forceGoose: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
: withValidation;
|
||||
return appendAgentRunValidationInstruction(
|
||||
withAgentRunValidationMetadata(normalized, options.validation),
|
||||
withRunMetadata,
|
||||
options.toolMode === 'code' ? options.validationInstruction : null,
|
||||
);
|
||||
}
|
||||
@@ -2440,6 +2458,7 @@ export async function createAgentRun(
|
||||
user_message: userMessagePayload,
|
||||
...(options.toolMode ? { tool_mode: options.toolMode } : {}),
|
||||
...(options.taskType ? { task_type: options.taskType } : {}),
|
||||
...(options.forceGoose ? { force_goose: true } : {}),
|
||||
}),
|
||||
},
|
||||
{ timeoutMs: AGENT_CONNECT_TIMEOUT_MS },
|
||||
|
||||
@@ -144,7 +144,7 @@ export function ChatPanel({
|
||||
text: string,
|
||||
imageUrls?: string[],
|
||||
previewImageUrls?: string[],
|
||||
options?: { messageId?: string },
|
||||
options?: { messageId?: string; forceGoose?: boolean },
|
||||
) => void | Promise<void>;
|
||||
onUploadImage?: (
|
||||
file: File,
|
||||
@@ -167,6 +167,7 @@ export function ChatPanel({
|
||||
const [pageSource, setPageSource] = useState<Message | null>(null);
|
||||
const [sharePreviewSource, setSharePreviewSource] = useState<Message | null>(null);
|
||||
const [voiceNotice, setVoiceNotice] = useState<string | null>(null);
|
||||
const [forceGoose, setForceGoose] = useState(false);
|
||||
const [pendingImages, setPendingImages] = useState<PendingChatImage[]>([]);
|
||||
const [uploadingImage, setUploadingImage] = useState(false);
|
||||
const [imageError, setImageError] = useState<string | null>(null);
|
||||
@@ -514,7 +515,10 @@ export function ChatPanel({
|
||||
}));
|
||||
const imagesToSend = uploadedUrls.filter(Boolean);
|
||||
const previewImagesToSend = imagesToSend;
|
||||
await onSubmit(trimmed, imagesToSend, previewImagesToSend, { messageId: outgoingMessageId });
|
||||
await onSubmit(trimmed, imagesToSend, previewImagesToSend, {
|
||||
messageId: outgoingMessageId,
|
||||
forceGoose,
|
||||
});
|
||||
uploadedImages.forEach(revokePendingImage);
|
||||
setPendingImages([]);
|
||||
} catch (err) {
|
||||
@@ -530,7 +534,7 @@ export function ChatPanel({
|
||||
}
|
||||
setUploadingImage(false);
|
||||
suppressVoiceUpdateRef.current = false;
|
||||
}, [input, onSubmit, onUploadImage, pendingImages, revokePendingImage, voiceDisabled, voiceRecording]);
|
||||
}, [forceGoose, input, onSubmit, onUploadImage, pendingImages, revokePendingImage, voiceDisabled, voiceRecording]);
|
||||
|
||||
const handleSubmit = useCallback(async () => {
|
||||
await submitText();
|
||||
@@ -822,6 +826,20 @@ export function ChatPanel({
|
||||
onPrefill={setInput}
|
||||
/>
|
||||
)}
|
||||
{!showHomeWelcome && (
|
||||
<label
|
||||
className={`chat-force-goose-toggle${forceGoose ? ' is-active' : ''}`}
|
||||
title="勾选后,本轮消息强制交给 Goose 执行"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={forceGoose}
|
||||
disabled={voiceDisabled}
|
||||
onChange={(event) => setForceGoose(event.target.checked)}
|
||||
/>
|
||||
<span>强制走 Goose</span>
|
||||
</label>
|
||||
)}
|
||||
{chatState === 'streaming' ? (
|
||||
<button type="button" className="danger-btn chat-input-action" onClick={() => void onStop()}>
|
||||
停止
|
||||
|
||||
@@ -541,7 +541,12 @@ export function ChatView({
|
||||
capabilities={capabilities}
|
||||
grantedSkills={grantedSkills}
|
||||
onSubmit={(text, imageUrls, previewImageUrls, options) =>
|
||||
void submit(text, { messageId: options?.messageId }, imageUrls, previewImageUrls)
|
||||
void submit(
|
||||
text,
|
||||
{ messageId: options?.messageId, forceGoose: options?.forceGoose },
|
||||
imageUrls,
|
||||
previewImageUrls,
|
||||
)
|
||||
}
|
||||
onUploadImage={(file, onProgress, options) =>
|
||||
uploadChatImage(file, onProgress, { messageId: options?.messageId })
|
||||
|
||||
@@ -140,8 +140,22 @@ export function SpaceChatPanel({
|
||||
grantedSkills={grantedSkills}
|
||||
onSubmit={(text, imageUrls, previewImageUrls, options) =>
|
||||
chatBridge
|
||||
? void submit(text, { ...context, messageId: options?.messageId }, imageUrls, previewImageUrls)
|
||||
: void submit(text, { mindspaceContext: context, messageId: options?.messageId }, imageUrls, previewImageUrls)
|
||||
? void submit(
|
||||
text,
|
||||
{ ...context, messageId: options?.messageId, forceGoose: options?.forceGoose },
|
||||
imageUrls,
|
||||
previewImageUrls,
|
||||
)
|
||||
: void submit(
|
||||
text,
|
||||
{
|
||||
mindspaceContext: context,
|
||||
messageId: options?.messageId,
|
||||
forceGoose: options?.forceGoose,
|
||||
},
|
||||
imageUrls,
|
||||
previewImageUrls,
|
||||
)
|
||||
}
|
||||
onUploadImage={(file, onProgress, options) =>
|
||||
uploadChatImage(file, onProgress, { messageId: options?.messageId })
|
||||
|
||||
@@ -1136,7 +1136,7 @@ export function useTKMindChat(
|
||||
const submit = useCallback(
|
||||
async (
|
||||
text: string,
|
||||
options?: { mindspaceContext?: MindSpaceChatContext; messageId?: string },
|
||||
options?: { mindspaceContext?: MindSpaceChatContext; messageId?: string; forceGoose?: boolean },
|
||||
imageUrls?: string[],
|
||||
previewImageUrls?: string[],
|
||||
) => {
|
||||
@@ -1182,16 +1182,20 @@ export function useTKMindChat(
|
||||
}
|
||||
|
||||
try {
|
||||
const runOptions = resolveAgentRunOptions(trimmed, {
|
||||
taskType: 'h5_chat_code_task',
|
||||
userId: userRef.current?.id ?? null,
|
||||
requestId,
|
||||
mindspaceContext: options?.mindspaceContext ?? null,
|
||||
});
|
||||
const createdRun = await createAgentRun(
|
||||
activeSessionId,
|
||||
requestId,
|
||||
userMessage,
|
||||
resolveAgentRunOptions(trimmed, {
|
||||
taskType: 'h5_chat_code_task',
|
||||
userId: userRef.current?.id ?? null,
|
||||
requestId,
|
||||
mindspaceContext: options?.mindspaceContext ?? null,
|
||||
}),
|
||||
{
|
||||
...runOptions,
|
||||
...(options?.forceGoose ? { forceGoose: true } : {}),
|
||||
},
|
||||
);
|
||||
const finishedRun =
|
||||
createdRun.status === 'succeeded' ? createdRun : await waitForAgentRun(createdRun.id);
|
||||
|
||||
@@ -2702,6 +2702,50 @@ body,
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.chat-force-goose-toggle {
|
||||
display: inline-flex;
|
||||
flex: 0 0 auto;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
min-height: 38px;
|
||||
padding: 7px 10px;
|
||||
border: 1px solid var(--color-border-input);
|
||||
border-radius: var(--radius-lg);
|
||||
color: var(--color-text-secondary);
|
||||
background: var(--color-bg-elevated);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
line-height: 1.1;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.chat-force-goose-toggle:hover {
|
||||
color: var(--color-text-primary);
|
||||
border-color: var(--color-border-strong);
|
||||
background: var(--color-bg-hover);
|
||||
}
|
||||
|
||||
.chat-force-goose-toggle input {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
margin: 0;
|
||||
accent-color: #79b7ff;
|
||||
}
|
||||
|
||||
.chat-force-goose-toggle.is-active {
|
||||
color: #eff6ff;
|
||||
border-color: rgba(121, 183, 255, 0.6);
|
||||
background: rgba(121, 183, 255, 0.14);
|
||||
}
|
||||
|
||||
.chat-force-goose-toggle:has(input:disabled) {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.45;
|
||||
}
|
||||
|
||||
.chat-skill-trigger {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
@@ -9249,6 +9293,16 @@ body,
|
||||
padding-inline: 12px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.chat-force-goose-toggle {
|
||||
min-height: 36px;
|
||||
padding-inline: 8px;
|
||||
}
|
||||
|
||||
.chat-force-goose-toggle span {
|
||||
max-width: 56px;
|
||||
white-space: normal;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { MindSpaceChatContext } from '../types';
|
||||
export type AgentRunCreateOptions = {
|
||||
toolMode?: 'chat' | 'code';
|
||||
taskType?: string | null;
|
||||
forceGoose?: boolean;
|
||||
validation?: AgentRunValidation | null;
|
||||
validationInstruction?: string | null;
|
||||
};
|
||||
@@ -52,6 +53,16 @@ const CODE_TASK_PATTERNS = [
|
||||
/(代码|仓库|项目|文件|组件|接口).{0,12}(修改|修复|重构|调试|实现|新增|编写|更新)/,
|
||||
];
|
||||
|
||||
const GOOSE_TASK_PATTERNS = [
|
||||
/\b(public\/[^\s"'<>]+\.html)\b/i,
|
||||
/\b(html|h5|web\s?page|landing\s?page|microsite|docx|word)\b/i,
|
||||
/\b(write|create|generate|publish|download|export|save|edit)\b.{0,40}\b(file|page|html|docx|word|asset)\b/i,
|
||||
/\b(file|page|html|docx|word|asset)\b.{0,40}\b(write|create|generate|publish|download|export|save|edit)\b/i,
|
||||
/(?:生成|创建|制作|做|写|设计|发布|导出|下载|保存|修改|编辑).{0,24}(?:页面|网页|HTML|html|H5|h5|文件|文档|Word|word|docx|下载页|公开页|分享页|落地页|活动页)/u,
|
||||
/(?:页面|网页|HTML|html|H5|h5|文件|文档|Word|word|docx|下载页|公开页|分享页|落地页|活动页).{0,24}(?:生成|创建|制作|做|写|设计|发布|导出|下载|保存|修改|编辑)/u,
|
||||
/MindSpace\/[^/\s]+\/public\/[^\s"'<>]+\.html/i,
|
||||
];
|
||||
|
||||
function sanitizeRequestIdForPath(requestId: string): string {
|
||||
const normalized = String(requestId ?? '').trim().replace(/[^a-zA-Z0-9._-]/g, '-');
|
||||
return normalized || 'unknown-request';
|
||||
@@ -224,10 +235,18 @@ export function resolveAgentRunOptions(
|
||||
pageEdit?: { pageId?: string | null; pageTitle?: string | null } | null;
|
||||
} = {},
|
||||
): AgentRunCreateOptions {
|
||||
if (!agentCodeRunsEnabledForUser(userId)) return {};
|
||||
const normalizedText = String(text ?? '').trim();
|
||||
const shouldForceGoose =
|
||||
forceCode || GOOSE_TASK_PATTERNS.some((pattern) => pattern.test(normalizedText));
|
||||
|
||||
if (!agentCodeRunsEnabledForUser(userId)) {
|
||||
return shouldForceGoose ? { forceGoose: true, taskType } : {};
|
||||
}
|
||||
|
||||
const shouldUseCode = forceCode || (allowAutodetect && CODE_TASK_PATTERNS.some((pattern) => pattern.test(normalizedText)));
|
||||
if (!shouldUseCode) return {};
|
||||
if (!shouldUseCode) {
|
||||
return shouldForceGoose ? { forceGoose: true, taskType } : {};
|
||||
}
|
||||
const normalizedRequestId = requestId ?? crypto.randomUUID();
|
||||
const receipt = buildAgentRunValidationReceipt(normalizedRequestId);
|
||||
const taskValidation = buildAgentRunTaskValidation({
|
||||
@@ -240,6 +259,7 @@ export function resolveAgentRunOptions(
|
||||
return {
|
||||
toolMode: 'code',
|
||||
taskType,
|
||||
forceGoose: true,
|
||||
validation: mergeAgentRunValidationFiles(receipt.validation, taskValidation.validation),
|
||||
validationInstruction: `${receipt.instruction}${taskValidation.instruction}`,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user