Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
- **@tag routing** in Telegram — `@researcher find AI trends` sends the message to the right agent
- **Coordinator delegation** — one agent orchestrates the rest, delegating tasks automatically
- **Shared memory** — agents read each other's output files for seamless collaboration
- **120+ built-in skills** — SEO (19 skills + 30 reference files), ads (18 skills + 23 reference files), content, social media, analytics, growth hacking
- **121 built-in skills** — including 43 marketing skills plus SEO, ads, content, social media, analytics, and growth hacking workflows
- **25 built-in MCP servers** — browser automation, AI search, SEO, ads, social media, email marketing, CRM, disposable email inboxes, multimodal generation (image/video/speech/music), and more. Enable what you need from the Web UI
- **52 marketing tools** via Citedy MCP server
- **Instant file publishing** — upload any file to [here.now](https://here.now), get a shareable link, host static sites, use your own domain
Expand Down
51 changes: 26 additions & 25 deletions console/src/pages/Chat/index.module.less
Original file line number Diff line number Diff line change
Expand Up @@ -23,32 +23,28 @@
}

.liveProgress {
position: absolute;
left: 50%;
bottom: 92px;
z-index: 20;
width: min(560px, calc(100% - 48px));
width: 100%;
margin: 6px 0 8px;
pointer-events: none;
transform: translateX(-50%);
}

.liveProgressInner {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 14px;
border: 1px solid rgba(203, 213, 225, 0.72);
border-radius: 999px;
background: rgba(255, 255, 255, 0.92);
gap: 8px;
padding: 0 6px;
border: 0;
border-radius: 0;
background: transparent;
color: var(--citedy-slate-700, #334155);
box-shadow: 0 18px 38px rgba(15, 23, 42, 0.12);
backdrop-filter: blur(14px);
-webkit-backdrop-filter: blur(14px);
box-shadow: none;
backdrop-filter: none;
-webkit-backdrop-filter: none;
}

.liveProgressPulse {
width: 10px;
height: 10px;
width: 7px;
height: 7px;
border-radius: 999px;
background: #16a34a;
box-shadow: 0 0 0 0 rgba(22, 163, 74, 0.42);
Expand All @@ -57,15 +53,18 @@
}

.liveProgressError {
width: 10px;
height: 10px;
width: 7px;
height: 7px;
border-radius: 999px;
background: #f97316;
box-shadow: 0 0 0 4px rgba(249, 115, 22, 0.16);
flex: 0 0 auto;
}

.liveProgressText {
display: flex;
align-items: baseline;
gap: 6px;
min-width: 0;
flex: 1 1 auto;
}
Expand All @@ -78,7 +77,6 @@
}

.liveProgressDetail {
margin-top: 2px;
font-size: 12px;
line-height: 1.35;
color: var(--citedy-slate-500, #64748b);
Expand Down Expand Up @@ -123,10 +121,9 @@

@media (prefers-color-scheme: dark) {
.liveProgressInner {
border-color: rgba(71, 85, 105, 0.78);
background: rgba(15, 23, 42, 0.9);
background: transparent;
color: var(--citedy-slate-300, #cbd5e1);
box-shadow: 0 18px 38px rgba(0, 0, 0, 0.28);
box-shadow: none;
}

.liveProgressTitle {
Expand All @@ -145,13 +142,17 @@

@media (max-width: 640px) {
.liveProgress {
bottom: 84px;
width: calc(100% - 24px);
margin-bottom: 8px;
}

.liveProgressInner {
align-items: center;
}

.liveProgressText {
align-items: flex-start;
border-radius: 18px;
flex-direction: column;
gap: 1px;
}

.liveProgressElapsed {
Expand Down
115 changes: 107 additions & 8 deletions console/src/pages/Chat/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -758,6 +758,45 @@ function LiveProgressStatus({ status }: { status: LiveChatStatus }) {
);
}

function getChatScrollElement(root: HTMLElement | null): HTMLElement | null {
if (!root) return null;

const messageLists = Array.from(
root.querySelectorAll<HTMLElement>('[class*="chat-anywhere-message-list"]'),
);

return (
messageLists.find(
(element) => element.scrollHeight > element.clientHeight,
) ??
messageLists.find((element) =>
String(element.className).includes("bubble-list-wrapper"),
) ??
null
);
}

function isNearChatBottom(element: HTMLElement): boolean {
return element.scrollHeight - element.scrollTop - element.clientHeight < 160;
}

function scrollChatToBottom(
root: HTMLElement | null,
behavior: ScrollBehavior = "smooth",
) {
const scrollElement = getChatScrollElement(root);
if (!scrollElement) return;

scrollElement.scrollTo({
top: scrollElement.scrollHeight,
behavior,
});

window.requestAnimationFrame(() => {
scrollElement.scrollTop = scrollElement.scrollHeight;
});
}

export default function ChatPage() {
const { t } = useTranslation();
const navigate = useNavigate();
Expand All @@ -768,7 +807,9 @@ export default function ChatPage() {
const [liveChatStatus, setLiveChatStatus] = useState<LiveChatStatus | null>(
null,
);
const chatStageRef = useRef<HTMLDivElement>(null);
const clearStatusTimers = useRef<number[]>([]);
const forceFollowUntilRef = useRef(0);
const [optionsConfig] = useLocalStorageState<OptionsConfig>(
"agent-scope-runtime-webui-options",
{
Expand All @@ -793,6 +834,55 @@ export default function ChatPage() {
};
}, []);

useEffect(() => {
const root = chatStageRef.current;
if (!root) return;

const followIfNeeded = (behavior: ScrollBehavior = "smooth") => {
const scrollElement = getChatScrollElement(root);
if (!scrollElement) return;

if (
Date.now() < forceFollowUntilRef.current ||
isNearChatBottom(scrollElement)
) {
scrollChatToBottom(root, behavior);
}
};

const mutationObserver = new MutationObserver(() => followIfNeeded());
mutationObserver.observe(root, {
childList: true,
characterData: true,
subtree: true,
});

const resizeObserver = new ResizeObserver(() => followIfNeeded());
resizeObserver.observe(root);

followIfNeeded("instant");

return () => {
mutationObserver.disconnect();
resizeObserver.disconnect();
};
}, [activeTab]);

useEffect(() => {
if (liveChatStatus) {
forceFollowUntilRef.current = Date.now() + 15_000;
scrollChatToBottom(chatStageRef.current);
return;
}

forceFollowUntilRef.current = Date.now() + 1_500;
const timer = window.setTimeout(() => {
scrollChatToBottom(chatStageRef.current, "instant");
}, 50);

return () => window.clearTimeout(timer);
}, [liveChatStatus?.requestId, liveChatStatus?.stage]);

const handleTabChange = (tabId: string) => {
setActiveTab(tabId);
if (tabId === "all") {
Expand Down Expand Up @@ -1173,18 +1263,28 @@ export default function ChatPage() {
sender: {
...optionsConfig?.sender,
beforeUI: (
<PersonaSelector
personas={personas}
selected={selectedPersona}
onSelect={setSelectedPersona}
/>
<>
<PersonaSelector
personas={personas}
selected={selectedPersona}
onSelect={setSelectedPersona}
/>
{liveChatStatus && <LiveProgressStatus status={liveChatStatus} />}
</>
),
},
customToolRenderConfig: {
"weather search mock": Weather,
},
} as unknown as IAgentScopeRuntimeWebUIOptions;
}, [optionsConfig, selectedPersona, activeTab, personas, currentSessionId]);
}, [
optionsConfig,
liveChatStatus,
selectedPersona,
activeTab,
personas,
currentSessionId,
]);

return (
<div
Expand All @@ -1202,9 +1302,8 @@ export default function ChatPage() {
onTabChange={handleTabChange}
/>
)}
<div className={styles.chatStage}>
<div className={styles.chatStage} ref={chatStageRef}>
<AgentScopeRuntimeWebUI key={activeTab} options={options} />
{liveChatStatus && <LiveProgressStatus status={liveChatStatus} />}
</div>

<Modal open={showModelPrompt} closable={false} footer={null} width={480}>
Expand Down
2 changes: 1 addition & 1 deletion src/adclaw/__version__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# -*- coding: utf-8 -*-
__version__ = "1.0.26"
__version__ = "1.0.27"
Loading
Loading