feat: Fix SEO agent behavior and add z.ai API validation
- Add "SEO-First" mode to prevent unwanted agent switching - SEO agent now stays locked and answers queries through SEO lens - Add [SUGGEST_AGENT:xxx] marker for smart agent suggestions - Add suggestion banner UI with Switch/Dismiss buttons - Prevent auto-switching mid-response - Add validateConnection() method to ZaiPlanService - Add debounced API key validation (500ms) in Settings - Add inline status indicators (valid/validating/error) - Add persistent validation cache (5min) in localStorage - Add "Test Connection" button for manual re-validation - Add clear error messages for auth failures - Add ApiValidationStatus interface - Add apiValidationStatus state for tracking connection states - Add setApiValidationStatus action - Real-time API key validation in Settings panel - Visual status indicators (✓/✗/🔄) - Agent suggestion banner with Switch/Dismiss actions Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -320,6 +320,7 @@ function parseStreamingContent(text: string, currentAgent: string) {
|
||||
let preview: PreviewData | null = null;
|
||||
let chatDisplay = text.trim();
|
||||
let status: string | null = null;
|
||||
let suggestedAgent: string | null = null;
|
||||
|
||||
const decodeHtml = (value: string) => value
|
||||
.replace(/</g, "<")
|
||||
@@ -332,7 +333,13 @@ function parseStreamingContent(text: string, currentAgent: string) {
|
||||
return fenced ? fenced[1].trim() : value.trim();
|
||||
};
|
||||
|
||||
// 1. Detect Agent (be flexible with brackets and keywords like APP/WEB/SEO)
|
||||
// 1. Detect SUGGEST_AGENT marker (user can choose to switch)
|
||||
const suggestMatch = text.match(/\[SUGGEST_AGENT:([\w-]+)\]/i);
|
||||
if (suggestMatch) suggestedAgent = suggestMatch[1].toLowerCase();
|
||||
|
||||
// 2. Detect Agent (be flexible with brackets and keywords like APP/WEB/SEO)
|
||||
// NOTE: We only respect agent switches if explicitly requested via [AGENT:xxx]
|
||||
// Auto-switching is disabled to prevent unwanted agent changes
|
||||
const agentMatch = text.match(/\[+(?:AGENT|content|seo|smm|pm|code|design|web|app):([\w-]+)\]+/i);
|
||||
if (agentMatch) agent = agentMatch[1].toLowerCase();
|
||||
|
||||
@@ -353,8 +360,8 @@ function parseStreamingContent(text: string, currentAgent: string) {
|
||||
|
||||
// 3. Clean display text - hide all tag-like sequences and their partials
|
||||
chatDisplay = text
|
||||
// Hide complete tags (flexible brackets)
|
||||
.replace(/\[+(?:AGENT|content|seo|smm|pm|code|design|web|app|PREVIEW|APP|WEB|SEO|CODE|DESIGN|SMM|PM|CONTENT|PREV):?[\w-]*:?[\w-]*\]+/gi, "")
|
||||
// Hide complete tags (flexible brackets), including SUGGEST_AGENT
|
||||
.replace(/\[+(?:AGENT|SUGGEST_AGENT|content|seo|smm|pm|code|design|web|app|PREVIEW|APP|WEB|SEO|CODE|DESIGN|SMM|PM|CONTENT|PREV):?[\w-]*:?[\w-]*\]+/gi, "")
|
||||
// Hide content inside preview block (cleanly)
|
||||
.replace(/\[+PREVIEW:[\w-]+:?[\w-]+?\]+[\s\S]*?(?:\[\/(?:PREVIEW|APP|WEB|SEO|CODE|DESIGN|SMM|PM|CONTENT)\]+|$)/gi, "")
|
||||
// Hide closing tags
|
||||
@@ -427,7 +434,7 @@ function parseStreamingContent(text: string, currentAgent: string) {
|
||||
chatDisplay = "Rendering live artifact...";
|
||||
}
|
||||
|
||||
return { chatDisplay, preview, agent, status };
|
||||
return { chatDisplay, preview, agent, status, suggestedAgent };
|
||||
}
|
||||
|
||||
// --- Main Component ---
|
||||
@@ -471,6 +478,9 @@ export default function AIAssist() {
|
||||
const [viewMode, setViewMode] = useState<"preview" | "code">("preview");
|
||||
const [abortController, setAbortController] = useState<AbortController | null>(null);
|
||||
|
||||
// Agent suggestion state
|
||||
const [suggestedAgent, setSuggestedAgent] = useState<string | null>(null);
|
||||
|
||||
// Agentic States
|
||||
const [assistStep, setAssistStep] = useState<"idle" | "plan" | "generating" | "preview">("idle");
|
||||
const [aiPlan, setAiPlan] = useState<any>(null);
|
||||
@@ -624,10 +634,17 @@ export default function AIAssist() {
|
||||
currentAgent,
|
||||
onChunk: (chunk) => {
|
||||
accumulated += chunk;
|
||||
const { chatDisplay, preview, agent, status: streamStatus } = parseStreamingContent(accumulated, currentAgent);
|
||||
const { chatDisplay, preview, agent, status: streamStatus, suggestedAgent: suggested } = parseStreamingContent(accumulated, currentAgent);
|
||||
|
||||
if (streamStatus) setStatus(streamStatus);
|
||||
|
||||
// Update suggested agent state
|
||||
if (suggested && suggested !== currentAgent) {
|
||||
setSuggestedAgent(suggested);
|
||||
} else {
|
||||
setSuggestedAgent(null);
|
||||
}
|
||||
|
||||
// Only update local state if we're still on the same tab
|
||||
if (activeTabId === requestTabId) {
|
||||
if (preview && JSON.stringify(preview) !== JSON.stringify(lastParsedPreview)) {
|
||||
@@ -883,6 +900,7 @@ export default function AIAssist() {
|
||||
onClick={() => {
|
||||
setCurrentAgent(agent);
|
||||
updateActiveTab({ currentAgent: agent });
|
||||
setSuggestedAgent(null); // Clear suggestion on manual switch
|
||||
}}
|
||||
className={cn(
|
||||
"flex items-center gap-2 px-3 py-2 rounded-full text-[11px] font-black uppercase tracking-widest border transition-all",
|
||||
@@ -896,6 +914,40 @@ export default function AIAssist() {
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Agent Suggestion Banner */}
|
||||
{suggestedAgent && suggestedAgent !== currentAgent && (
|
||||
<div className="p-3 rounded-xl bg-gradient-to-r from-blue-500/10 to-indigo-500/10 border border-blue-500/30 animate-in slide-in-from-top-2 duration-300">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-2 bg-blue-500/20 rounded-lg">
|
||||
<Orbit className="h-4 w-4 text-blue-500" />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<p className="text-xs font-semibold text-blue-700 dark:text-blue-300">
|
||||
The {t.agents[suggestedAgent as keyof typeof t.agents] || suggestedAgent} agent might be better suited for this task
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setCurrentAgent(suggestedAgent);
|
||||
updateActiveTab({ currentAgent: suggestedAgent });
|
||||
setSuggestedAgent(null);
|
||||
}}
|
||||
className="h-7 text-[10px] font-bold uppercase tracking-wider bg-blue-600 hover:bg-blue-500 text-white px-3 rounded-lg"
|
||||
>
|
||||
Switch
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setSuggestedAgent(null)}
|
||||
className="h-7 w-7 p-0 text-blue-600 hover:text-blue-800 hover:bg-blue-100 dark:text-blue-400 dark:hover:text-blue-200 dark:hover:bg-blue-900/30 rounded-lg"
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div ref={scrollRef} className="flex-1 overflow-y-auto px-6 py-6 space-y-8 scrollbar-thin scrollbar-thumb-blue-200/60 dark:scrollbar-thumb-blue-900">
|
||||
{/* Qwen Auth Banner */}
|
||||
|
||||
Reference in New Issue
Block a user