refactor: rename SolaceScanScout to Solace and update related configurations

- Updated branding from "SolaceScanScout" to "Solace" across various files including deployment scripts, API responses, and documentation.
- Changed default base URL for Playwright tests and updated security headers to reflect the new branding.
- Enhanced README and API documentation to include new authentication endpoints and product access details.

This refactor aligns the project branding and improves clarity in the API documentation.
This commit is contained in:
defiQUG
2026-04-10 12:52:17 -07:00
parent 6eef6b07f6
commit 0972178cc5
160 changed files with 13274 additions and 1061 deletions

View File

@@ -0,0 +1,178 @@
'use client'
import { FormEvent, useMemo, useState } from 'react'
import { usePathname } from 'next/navigation'
import { getExplorerApiBase } from '@/services/api/blockscout'
interface AgentMessage {
role: 'assistant' | 'user'
content: string
}
const QUICK_PROMPTS = [
'Explain this page',
'Summarize the chain status',
'Help me inspect a contract',
'Find likely navigation issues',
] as const
export default function ExplorerAgentTool() {
const pathname = usePathname() ?? '/'
const [open, setOpen] = useState(false)
const [input, setInput] = useState('')
const [submitting, setSubmitting] = useState(false)
const [messages, setMessages] = useState<AgentMessage[]>([
{
role: 'assistant',
content:
'Explorer AI Agent Tool is ready. I can explain this page, summarize what you are looking at, and help investigate transactions, contracts, routes, and system surfaces.',
},
])
const pageContext = useMemo(
() => ({
path: pathname,
view: 'explorer',
}),
[pathname],
)
const sendMessage = async (content: string) => {
const trimmed = content.trim()
if (!trimmed || submitting) return
const nextMessages: AgentMessage[] = [...messages, { role: 'user', content: trimmed }]
setMessages(nextMessages)
setInput('')
setSubmitting(true)
try {
const response = await fetch(`${getExplorerApiBase()}/api/v1/ai/chat`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
messages: nextMessages,
pageContext,
}),
})
const payload = await response.json().catch(() => null)
if (!response.ok) {
throw new Error(payload?.error?.message || `HTTP ${response.status}`)
}
const reply =
payload?.message?.content ||
payload?.reply ||
'The agent did not return a readable reply.'
setMessages((current) => [...current, { role: 'assistant', content: String(reply) }])
} catch (error) {
setMessages((current) => [
...current,
{
role: 'assistant',
content:
error instanceof Error
? `Agent tool is temporarily unavailable: ${error.message}`
: 'Agent tool is temporarily unavailable.',
},
])
} finally {
setSubmitting(false)
}
}
const handleSubmit = async (event: FormEvent) => {
event.preventDefault()
await sendMessage(input)
}
return (
<div className="fixed bottom-5 right-5 z-40 flex max-w-[calc(100vw-1.5rem)] flex-col items-end gap-3">
{open ? (
<section className="w-[min(24rem,calc(100vw-1.5rem))] overflow-hidden rounded-2xl border border-gray-200 bg-white shadow-2xl dark:border-gray-700 dark:bg-gray-900">
<div className="flex items-start justify-between gap-3 border-b border-gray-200 px-4 py-3 dark:border-gray-700">
<div>
<h2 className="text-sm font-semibold text-gray-900 dark:text-white">Explorer AI Agent Tool</h2>
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
Page-aware guidance for the explorer. Helpful, read-only, and designed for quick investigation.
</p>
</div>
<button
type="button"
onClick={() => setOpen(false)}
className="rounded-lg px-2 py-1 text-xs font-medium text-gray-500 hover:bg-gray-100 hover:text-gray-800 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-200"
>
Close
</button>
</div>
<div className="flex flex-wrap gap-2 border-b border-gray-200 px-4 py-3 dark:border-gray-700">
{QUICK_PROMPTS.map((prompt) => (
<button
key={prompt}
type="button"
onClick={() => void sendMessage(prompt)}
className="rounded-full border border-primary-200 bg-primary-50 px-3 py-1 text-xs font-medium text-primary-700 hover:bg-primary-100 dark:border-primary-500/30 dark:bg-primary-500/10 dark:text-primary-300 dark:hover:bg-primary-500/20"
>
{prompt}
</button>
))}
</div>
<div className="max-h-[22rem] space-y-3 overflow-y-auto px-4 py-3">
{messages.map((message, index) => (
<div
key={`${message.role}-${index}`}
className={`rounded-2xl px-3 py-2 text-sm ${
message.role === 'assistant'
? 'bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-100'
: 'ml-6 bg-primary-600 text-white'
}`}
>
{message.content}
</div>
))}
</div>
<form onSubmit={handleSubmit} className="border-t border-gray-200 px-4 py-3 dark:border-gray-700">
<label className="block">
<span className="sr-only">Ask the explorer agent</span>
<textarea
value={input}
onChange={(event) => setInput(event.target.value)}
rows={3}
placeholder="Ask about this page, a transaction, a token, or an access-control flow."
className="w-full rounded-xl border border-gray-300 px-3 py-2 text-sm focus:border-primary-500 focus:outline-none focus:ring-2 focus:ring-primary-200 dark:border-gray-700 dark:bg-gray-950 dark:text-white"
/>
</label>
<div className="mt-3 flex items-center justify-between gap-3">
<p className="text-xs text-gray-500 dark:text-gray-400">Current view: {pathname}</p>
<button
type="submit"
disabled={submitting || !input.trim()}
className="rounded-xl bg-primary-600 px-4 py-2 text-sm font-semibold text-white transition hover:bg-primary-700 disabled:cursor-not-allowed disabled:opacity-60"
>
{submitting ? 'Thinking…' : 'Send'}
</button>
</div>
</form>
</section>
) : null}
<button
type="button"
onClick={() => setOpen((value) => !value)}
className="inline-flex items-center gap-2 rounded-full bg-primary-600 px-4 py-3 text-sm font-semibold text-white shadow-lg transition hover:bg-primary-700"
aria-expanded={open}
>
<span className="inline-flex h-7 w-7 items-center justify-center rounded-full bg-white/15">
<svg className="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-4l-4 4v-4Z" />
</svg>
</span>
Agent Tool
</button>
</div>
)
}