# Conflicts:
#	frontend-web/src/open-canvas/components/ai-writing/AIWritingForm.tsx
This commit is contained in:
董宏凯 committed 2026-07-11 19:01:10 +08:00
commit 27e6b0b23e
718 files changed
+1168 -142636

No files matched your search

@@ -846,7 +846,10 @@ export const PromptInput = ({
ref={formRef}
{...props}
>
<InputGroup>{children}</InputGroup>
{/* Do not rely on InputGroup's :has(textarea) selector here: Chrome
versions before 105 ignore :has(), retain h-9, and clip the whole
multi-line composer to a single row. */}
<InputGroup className="h-auto min-h-16">{children}</InputGroup>
</form>
</PromptInputValidationContext.Provider>
);
@@ -873,16 +876,48 @@ export type PromptInputTextareaProps = ComponentProps<
typeof InputGroupTextarea
>;
export const PromptInputTextarea = ({
onChange,
className,
placeholder = "What would you like to know?",
...props
}: PromptInputTextareaProps) => {
const controller = useOptionalPromptInputController();
const attachments = usePromptInputAttachments();
const sanitizeIncomingFiles = usePromptInputValidation();
const [isComposing, setIsComposing] = useState(false);
export const PromptInputTextarea = ({
onChange,
onInput,
className,
placeholder = "What would you like to know?",
...props
}: PromptInputTextareaProps) => {
const controller = useOptionalPromptInputController();
const attachments = usePromptInputAttachments();
const sanitizeIncomingFiles = usePromptInputValidation();
const [isComposing, setIsComposing] = useState(false);
const textareaRef = useRef<HTMLTextAreaElement | null>(null);
const resizeForLegacyChrome = useCallback((textarea: HTMLTextAreaElement) => {
if (
typeof CSS !== "undefined" &&
typeof CSS.supports === "function" &&
CSS.supports("field-sizing", "content")
) {
return;
}
// Chrome 109 ignores field-sizing: content. Preserve the same 4rem–12rem
// autosize range used by min-h-16/max-h-48 instead of leaving the prompt
// stuck at its minimum height.
textarea.style.height = "auto";
const height = Math.max(64, Math.min(textarea.scrollHeight, 192));
textarea.style.height = `${height}px`;
textarea.style.overflowY = textarea.scrollHeight > 192 ? "auto" : "hidden";
}, []);
// onInput does not run for a controlled value filled by a suggested question
// or restored draft. Resize once after every render in legacy Chrome too.
useEffect(() => {
const textarea = textareaRef.current;
if (!textarea) return;
const frame = window.requestAnimationFrame(() => {
resizeForLegacyChrome(textarea);
});
return () => window.cancelAnimationFrame(frame);
}, [controller?.textInput.value, resizeForLegacyChrome]);
const handleKeyDown: KeyboardEventHandler<HTMLTextAreaElement> = (e) => {
if (e.key === "Enter") {
@@ -962,12 +997,18 @@ export const PromptInputTextarea = ({
};
return (
<InputGroupTextarea
className={cn("field-sizing-content max-h-48 min-h-16", className)}
name="message"
<InputGroupTextarea
className={cn("field-sizing-content max-h-48 min-h-16", className)}
name="message"
ref={textareaRef}
rows={3}
onCompositionEnd={() => setIsComposing(false)}
onCompositionStart={() => setIsComposing(true)}
onKeyDown={handleKeyDown}
onKeyDown={handleKeyDown}
onInput={(event) => {
resizeForLegacyChrome(event.currentTarget);
onInput?.(event);
}}
onPaste={handlePaste}
placeholder={placeholder}
{...props}
@@ -26,6 +26,7 @@ import {
import { Button } from "@/components/ui/button";
import { toast } from "sonner";
import { getStoredAuth } from "@/core/auth";
import { readRuntimeEnv } from "@/env";
import { queryClient } from "@/components/query-client-provider";
import { Checkbox } from "@/components/ui/checkbox";
import Empty from "./empty";
@@ -303,11 +304,11 @@ const DocumentUploadPopupContent: FC<{
const searchSpaceId = String(searchSpace?.id);
// 从环境变量获取配置
const backendUrl = import.meta.env.VITE_AIPI_BACKENP_URL || "http://localhost:8001";
const consumerUrl = import.meta.env.VITE_AIPI_CONSUMER_URL || "http://ch1.b.uat.4.cn/consumer";
const presentUrl = import.meta.env.VITE_AIPI_PRESENT_URL || "http://present.a.uat.4.cn/present/domain";
const knowesUrl = import.meta.env.VITE_AIPI_KNOWES_URL || "http://ch1.b.uat.4.cn/knowes";
const knowledgeUrl = import.meta.env.VITE_AIPI_KNOWLEDGE_URL || "http://chlib.b.uat.4.cn/knowlibrary";
const backendUrl = readRuntimeEnv("VITE_AIPI_BACKENP_URL") || "http://localhost:8001";
const consumerUrl = readRuntimeEnv("VITE_AIPI_CONSUMER_URL") || "http://ch1.b.uat.4.cn/consumer";
const presentUrl = readRuntimeEnv("VITE_AIPI_PRESENT_URL") || "http://present.a.uat.4.cn/present/domain";
const knowesUrl = readRuntimeEnv("VITE_AIPI_KNOWES_URL") || "http://ch1.b.uat.4.cn/knowes";
const knowledgeUrl = readRuntimeEnv("VITE_AIPI_KNOWLEDGE_URL") || "http://chlib.b.uat.4.cn/knowlibrary";
const [searchText, setSearchText] = useState("");
const [searchValue, setSearchValue] = useState("");
@@ -11,6 +11,7 @@ import { Button } from "@/components/ui/button";
import { toast } from "sonner";
import { getStoredAuth } from "@/core/auth";
import { authenticatedFetch } from "@/components/assistant-ui/document-upload-popup";
import { readRuntimeEnv } from "@/env";
interface NetworkActorProps {
@@ -186,9 +187,9 @@ export function NetworkActor({ inputValue, searchSpaceId, onSuccess }: NetworkAc
const [actorCount, setActorCount] = useState<number>(0);
const [directionType, setDirectionType] = useState<string>('TW');
const directionValue = useRef<string>('TW');
const consumerUrl = import.meta.env.VITE_AIPI_CONSUMER_URL || 'https://ch1.b.uat.4.cn/consumer';
const consumerUrl = readRuntimeEnv("VITE_AIPI_CONSUMER_URL") || 'https://ch1.b.uat.4.cn/consumer';
const backendUrl = typeof window !== 'undefined'
? (import.meta.env.VITE_BACKEND_BASE_URL || 'http://localhost:8001')
? (readRuntimeEnv("VITE_BACKEND_BASE_URL") || 'http://localhost:8001')
: 'http://localhost:8001';
const [selectedSearchSources, setSelectedSearchSources] = useState<UserDataItem[]>([]);
const [isImporting, setIsImporting] = useState(false);
@@ -508,8 +508,9 @@ export default function ProgressiveSkillsAnimation() {
"deploying",
"done",
].includes(phase) && (
<div className="text-base text-zinc-300">
<span className="text-purple-400">✨</span> Found 3 skills
<div className="flex items-center gap-2 text-base text-zinc-300">
<Sparkles size={16} className="text-purple-400" />
<span>Found 3 skills</span>
</div>
)}
@@ -526,8 +527,9 @@ export default function ProgressiveSkillsAnimation() {
].includes(phase) && (
<div className="mt-4">
<hr className="mb-3 border-zinc-700" />
<div className="mb-3 text-zinc-300">
🔬 Researching...
<div className="mb-3 inline-flex items-center gap-2 text-zinc-300">
<Search size={16} className="text-blue-400" />
<span>Researching...</span>
</div>
<div className="mb-3 space-y-2">
{/* Loading SKILL.md */}
@@ -623,7 +625,10 @@ export default function ProgressiveSkillsAnimation() {
className="mt-4"
>
<hr className="mb-3 border-zinc-700" />
<div className="mb-3 text-zinc-300">🔨 Building...</div>
<div className="mb-3 inline-flex items-center gap-2 text-zinc-300">
<FileText size={16} className="text-zinc-400" />
<span>Building...</span>
</div>
<div className="mb-3 flex items-center gap-2 pl-4 text-zinc-400">
<FileText size={16} />
<span>Loading frontend-design/SKILL.md...</span>
@@ -653,7 +658,10 @@ export default function ProgressiveSkillsAnimation() {
className="mt-4"
>
<hr className="mb-3 border-zinc-700" />
<div className="mb-3 text-zinc-300">🚀 Deploying...</div>
<div className="mb-3 inline-flex items-center gap-2 text-zinc-300">
<Terminal size={16} className="text-zinc-400" />
<span>Deploying...</span>
</div>
<div className="mb-3 space-y-2">
<div className="flex items-center gap-2 pl-4 text-zinc-400">
<FileText size={16} />
@@ -676,8 +684,9 @@ export default function ProgressiveSkillsAnimation() {
animate={{ opacity: 1, scale: 1 }}
className="mt-4 rounded-xl border border-green-500/30 bg-green-500/10 p-4"
>
<div className="text-lg font-medium text-green-500">
✅ Live at biotech-startup.vercel.app
<div className="inline-flex items-center gap-2 text-lg font-medium text-green-500">
<Check size={18} />
<span>Live at biotech-startup.vercel.app</span>
</div>
</motion.div>
)}
@@ -35,27 +35,27 @@ export function SandboxSection({ className }: { className?: string }) {
$ pip install -r requirements.txt
</TypingAnimation>
<AnimatedSpan delay={2000} className="text-green-500">
✔ Installed pygame
[ok] Installed pygame
</AnimatedSpan>
<TypingAnimation delay={2400}>
$ write game.py --lines 156
</TypingAnimation>
<AnimatedSpan delay={3200} className="text-blue-500">
✔ Written 156 lines
[ok] Written 156 lines
</AnimatedSpan>
<TypingAnimation delay={3600}>
$ python game.py --test
</TypingAnimation>
<AnimatedSpan delay={4200} className="text-green-500">
✔ All sprites loaded
[ok] All sprites loaded
</AnimatedSpan>
<AnimatedSpan delay={4500} className="text-green-500">
✔ Physics engine OK
[ok] Physics engine OK
</AnimatedSpan>
<AnimatedSpan delay={4800} className="text-green-500">
✔ 60 FPS stable
[ok] 60 FPS stable
</AnimatedSpan>
{/* Scene 2: Data Analysis */}
+13 -10
View File
@@ -144,21 +144,24 @@ function InputGroupInput({
);
}
function InputGroupTextarea({
className,
...props
}: React.ComponentProps<"textarea">) {
const InputGroupTextarea = React.forwardRef<
HTMLTextAreaElement,
React.ComponentProps<"textarea">
>(function InputGroupTextarea({ className, ...props }, ref) {
return (
<Textarea
data-slot="input-group-control"
<Textarea
data-slot="input-group-control"
ref={ref}
className={cn(
"flex-1 resize-none rounded-none border-0 bg-transparent py-3 shadow-none focus-visible:ring-0 dark:bg-transparent",
className,
)}
{...props}
/>
);
}
{...props}
/>
);
});
InputGroupTextarea.displayName = "InputGroupTextarea";
export {
InputGroup,
+19 -10
View File
@@ -2,19 +2,28 @@ import * as React from "react";
import { cn } from "@/lib/utils";
function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
return (
<textarea
data-slot="textarea"
className={cn(
"border-input placeholder:text-muted-foreground/60 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
const Textarea = React.forwardRef<
HTMLTextAreaElement,
React.ComponentProps<"textarea">
>(function Textarea({ className, rows, ...props }, ref) {
// `field-sizing` is not available in older Chrome. Keep a native rows
// fallback so a multi-line editor never collapses into a single line.
return (
<textarea
data-slot="textarea"
ref={ref}
rows={rows ?? 3}
className={cn(
"border-input placeholder:text-muted-foreground/60 flex min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
"dark:border-[#1B477E] dark:bg-[#022248] dark:text-[#AACEFF] ",
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
className,
)}
{...props}
/>
);
}
{...props}
/>
);
});
Textarea.displayName = "Textarea";
export { Textarea };
@@ -3,7 +3,7 @@ export function EmptyDocumentState() {
return (
<div className="flex h-full items-center justify-center bg-white px-6 text-center dark:bg-slate-900">
<div className="rounded-2xl border border-dashed border-slate-300 bg-slate-50 px-10 py-12 sm:px-12 dark:border-slate-700 dark:bg-slate-800/50">
<div className="mb-4 text-[40px] leading-none">📚</div>
<div className="mb-4 text-[28px] font-semibold leading-none text-slate-500 dark:text-slate-300">文档</div>
<div className="mb-2 text-lg font-bold text-slate-700 dark:text-slate-200">
选择一篇文档开始阅读
</div>
@@ -213,7 +213,7 @@ const errorToastDarkOptions = {
</div>
<div className="flex flex-1 flex-wrap items-center gap-1.5">
{summary && Object.keys(summary.by_category).length === 0 && (
<span className="text-muted-foreground text-sm">暂无失败记录 🎉</span>
<span className="text-muted-foreground text-sm">暂无失败记录</span>
)}
{summary &&
Object.entries(summary.by_category)
@@ -35,7 +35,7 @@ export function AgentWelcome({
className,
)}
>
<div className="bg-primary/10 ring-primary/15 mb-2 flex h-[72px] w-[72px] items-center justify-center rounded-3xl ring-1">
<div className="agent-avatar bg-primary/10 ring-primary/15 mb-2 flex h-[72px] w-[72px] items-center justify-center rounded-3xl ring-1">
<BotIcon className="text-primary h-9 w-9" />
</div>
<div className="text-3xl font-bold tracking-tight">
@@ -839,7 +839,7 @@ export function AgentCard({ agent, layout = "card", onPublisherClick }: AgentCar
<CardHeader className="min-w-0 pb-2">
<div className="flex min-w-0 items-start justify-between gap-2">
<div className="flex min-w-0 flex-1 items-center gap-2 overflow-hidden">
<div className="bg-primary/10 text-primary flex h-9 w-9 shrink-0 items-center justify-center rounded-lg">
<div className="agent-avatar bg-primary/10 text-primary flex h-9 w-9 shrink-0 items-center justify-center rounded-lg">
<BotIcon className="h-5 w-5" />
</div>
<div className="min-w-0 flex-1 overflow-hidden">
@@ -894,7 +894,7 @@ export function AgentCard({ agent, layout = "card", onPublisherClick }: AgentCar
const listLayout = (
<div className="group flex min-w-0 items-center gap-3 rounded-lg border p-2.5 transition-shadow hover:shadow-sm dark:border-[#213D5E] dark:bg-[#02244C]">
<div className="bg-primary/10 text-primary flex h-9 w-9 shrink-0 items-center justify-center rounded-lg">
<div className="agent-avatar bg-primary/10 text-primary flex h-9 w-9 shrink-0 items-center justify-center rounded-lg">
<BotIcon className="h-5 w-5" />
</div>
<div className="min-w-0 flex-1 overflow-hidden">
@@ -980,7 +980,7 @@ export function AgentCard({ agent, layout = "card", onPublisherClick }: AgentCar
? "正在加载 SOUL.md..."
: "输入 SOUL.md 内容"
}
className="max-h-[45vh] min-h-48 resize-y font-mono text-xs"
className="max-h-[45vh] min-h-48 resize-y text-xs"
/>
</div>
<div className="space-y-2">
@@ -264,7 +264,7 @@ export function AgentChatSidebar({ activeAgentId }: { activeAgentId?: string })
isActive
? isDark
? "border-[#2b8ffe] bg-[#0b4fae]/65 shadow-[0_0_0_1px_rgba(43,143,254,0.5),0_12px_28px_rgba(37,128,255,0.25)]"
: "border-primary/50 bg-primary/10 shadow-sm"
: "agent-sidebar-card--active border-primary/50 bg-primary/10 shadow-sm"
: isDark
? "border-[#2b8ffe]/20 bg-[#042b59]/35 hover:border-[#2b8ffe]/60 hover:bg-[#063b77]/60"
: "border-transparent hover:border-border hover:bg-accent/50",
@@ -520,7 +520,7 @@ export function AgentDetailPanel({
{/* ── Identity / 基础信息 ─────────────────────────────────────────── */}
<section className="bg-card/60 rounded-xl border p-4">
<div className="flex items-start gap-3">
<div className="bg-primary/10 ring-primary/15 flex size-12 shrink-0 items-center justify-center rounded-2xl ring-1">
<div className="agent-avatar bg-primary/10 ring-primary/15 flex size-12 shrink-0 items-center justify-center rounded-2xl ring-1">
<BotIcon className="text-primary size-6" />
</div>
<div className="min-w-0 flex-1 space-y-1.5">
@@ -830,8 +830,8 @@ export function AgentDetailPanel({
const panelChrome = (collapseButton: ReactNode) => (
<>
<div className="flex h-12 shrink-0 items-center justify-between border-b px-4">
<span className="text-sm font-semibold">智能体信息</span>
<div className="flex items-center gap-1">
<span className="shrink-0 whitespace-nowrap text-sm font-semibold">智能体信息</span>
<div className="flex min-w-0 items-center gap-1">
{headerActions}
{compactAppearanceActions}
{collapseButton}
@@ -138,7 +138,7 @@ export function AgentModuleSettingsDialog({
key={agent.id}
className="flex items-start gap-3 rounded-xl border border-border/60 bg-card p-3"
>
<div className="bg-primary/10 text-primary mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-lg">
<div className="agent-avatar bg-primary/10 text-primary mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-lg">
<Bot className="size-4" />
</div>
<div className="min-w-0 flex-1">
@@ -223,7 +223,7 @@ export function AgentModuleSidebar({
isActive
? isDark
? "border-[#2b8ffe] bg-[#0b4fae]/65 shadow-[0_0_0_1px_rgba(43,143,254,0.5),0_12px_28px_rgba(37,128,255,0.25)]"
: "border-primary/50 bg-primary/10 shadow-sm"
: "agent-sidebar-card--active border-primary/50 bg-primary/10 shadow-sm"
: isDark
? "border-[#2b8ffe]/20 bg-[#042b59]/35 hover:border-[#2b8ffe]/60 hover:bg-[#063b77]/60"
: "border-transparent hover:border-border hover:bg-accent/50",
@@ -20,16 +20,16 @@ export function CompactWelcome({ className }: { className?: string }) {
className="mb-3.5 inline-flex items-center justify-center rounded-full p-3"
style={{
backgroundColor:
"color-mix(in srgb, var(--workspace-brand) 5%, transparent)",
"rgb(var(--legacy-workspace-brand-rgb) / 5%)",
border:
"1px solid color-mix(in srgb, var(--workspace-brand) 12%, transparent)",
"1px solid rgb(var(--legacy-workspace-brand-rgb) / 12%)",
}}
>
<div
className="flex size-14 items-center justify-center rounded-2xl shadow-md"
style={{
backgroundImage:
"linear-gradient(to top right, var(--workspace-brand), color-mix(in srgb, var(--workspace-brand) 55%, white))",
"linear-gradient(to top right, var(--workspace-brand), var(--brand-200))",
}}
>
<span className="text-2xl font-black tracking-wide text-white italic">
@@ -38,8 +38,8 @@ export function CompactWelcome({ className }: { className?: string }) {
</div>
</div>
<h1 className="flex items-center justify-center gap-2 text-2xl font-bold tracking-tight text-foreground/90">
<span className="text-2xl">👋</span> 欢迎使用CM助手
<h1 className="flex items-center justify-center text-2xl font-bold tracking-tight text-foreground/90">
欢迎使用CM助手
</h1>
<p className="text-muted-foreground mx-auto mt-2 max-w-sm text-xs leading-relaxed">
基于先进深度推理模型,为您提供高效的市场分析、专业报告撰写、调研设计与协同脑暴支持。
@@ -320,7 +320,7 @@ export function GeneralQaSidebar() {
className={cn(
"flex h-10 min-w-0 flex-1 items-center justify-center gap-1.5 rounded-[5px] text-[14px] font-semibold transition-colors",
"border border-solid",
"bg-[color-mix(in_srgb,var(--primary)_14%,transparent)] hover:bg-[color-mix(in_srgb,var(--primary)_24%,transparent)]",
"bg-[rgb(var(--legacy-primary-rgb)/14%)] hover:bg-[rgb(var(--legacy-primary-rgb)/24%)]",
)}
>
<Plus className="size-4 stroke-[2.5]" />
@@ -353,7 +353,7 @@ export function GeneralQaSidebar() {
isActive
? isDark
? "border-[#2b8ffe] bg-[#0b4fae]/65 shadow-[0_0_0_1px_rgba(43,143,254,0.5),0_12px_28px_rgba(37,128,255,0.25)]"
: "border-primary/50 bg-primary/10 shadow-sm"
: "agent-sidebar-card--active border-primary/50 bg-primary/10 shadow-sm"
: isDark
? "border-[#2b8ffe]/20 bg-[#042b59]/35 hover:border-[#2b8ffe]/60 hover:bg-[#063b77]/60"
: "border-transparent hover:border-border hover:bg-accent/50",
@@ -613,7 +613,7 @@ export function InputBox({
: "*:data-[slot='input-group']:rounded-3xl",
// 紧凑落地态(CM 参考图):白卡 + 细边 + 轻阴影 + 主色聚焦环(无旋转彩边)
compact && isNewThread
? "rounded-2xl border border-gray-200/70 bg-white overflow-hidden shadow-[0_4.5px_22px_rgba(0,0,0,0.025)] transition-all duration-300 focus-within:border-[color-mix(in_srgb,var(--primary)_35%,transparent)] focus-within:ring-4 focus-within:ring-[color-mix(in_srgb,var(--primary)_5%,transparent)] focus-within:shadow-[0_10px_35px_color-mix(in_srgb,var(--primary)_4%,transparent)] dark:border-slate-700 dark:bg-slate-800"
? "rounded-2xl border border-gray-200/70 bg-white overflow-hidden shadow-[0_4.5px_22px_rgba(0,0,0,0.025)] transition-all duration-300 focus-within:border-[rgb(var(--legacy-primary-rgb)/35%)] focus-within:ring-4 focus-within:ring-[rgb(var(--legacy-primary-rgb)/5%)] focus-within:shadow-[0_10px_35px_rgb(var(--legacy-primary-rgb)/4%)] dark:border-slate-700 dark:bg-slate-800"
: // landing 态:透明,让外层 gemini-gradient-border 提供白卡 + 旋转描边
// 对话态:常规白卡 + 中性边框 + 轻阴影
isNewThread
@@ -680,18 +680,21 @@ export function InputBox({
<PromptInputButton
className={cn(
"gap-1.5! rounded-full px-3! shadow-none",
compact && "rounded-lg px-2.5! py-1! text-[11px] font-medium",
context.writing_mode
? "border border-amber-300 bg-amber-50 text-amber-700 hover:bg-amber-100 dark:border-amber-700 dark:bg-amber-950/40 dark:text-amber-200"
: compact
? "border border-transparent bg-muted/40 text-muted-foreground hover:bg-muted hover:text-foreground"
: "border-0 bg-transparent text-muted-foreground hover:bg-muted/70 hover:text-foreground dark:text-slate-400 dark:hover:bg-slate-700/70 dark:hover:text-slate-100",
)}
onClick={handleWritingModeToggle}
>
<FileTextIcon className="size-3" />
<span className="text-xs font-normal">写作</span>
</PromptInputButton>
compact && "rounded-lg px-2.5! py-1! text-[11px] font-medium",
context.writing_mode
? "border border-amber-300 bg-amber-50 text-amber-700 ring-1 ring-amber-300/35 hover:bg-amber-100 dark:border-amber-300/70 dark:bg-amber-400/20 dark:text-amber-100 dark:ring-amber-300/35 dark:shadow-[0_0_0_1px_rgba(251,191,36,0.18),0_0_18px_rgba(251,191,36,0.18)] dark:hover:bg-amber-400/25"
: compact
? "border border-transparent bg-muted/40 text-muted-foreground hover:bg-muted hover:text-foreground"
: "border-0 bg-transparent text-muted-foreground hover:bg-muted/70 hover:text-foreground dark:text-slate-400 dark:hover:bg-slate-700/70 dark:hover:text-slate-100",
)}
onClick={handleWritingModeToggle}
>
{context.writing_mode && (
<CheckIcon className="size-3 text-amber-700 dark:text-amber-100" />
)}
<FileTextIcon className="size-3" />
<span className="text-xs font-normal">写作</span>
</PromptInputButton>
</Tooltip>
<Tooltip
content={
@@ -702,19 +705,22 @@ export function InputBox({
>
<PromptInputButton
className={cn(
"gap-1.5! rounded-full px-3! shadow-none",
compact && "rounded-lg px-2.5! py-1! text-[11px] font-medium",
memoryInjectionEnabled
? "border border-blue-300 bg-blue-50 text-blue-700 hover:bg-blue-100 dark:border-blue-700 dark:bg-blue-950/40 dark:text-blue-200"
: compact
? "border border-transparent bg-muted/40 text-muted-foreground hover:bg-muted hover:text-foreground"
: "border-0 bg-transparent text-muted-foreground hover:bg-muted/70 hover:text-foreground dark:text-slate-400 dark:hover:bg-slate-700/70 dark:hover:text-slate-100",
)}
onClick={handleMemoryInjectionToggle}
>
<BrainCircuitIcon className="size-3" />
<span className="text-xs font-normal">记忆</span>
</PromptInputButton>
"gap-1.5! rounded-full px-3! shadow-none",
compact && "rounded-lg px-2.5! py-1! text-[11px] font-medium",
memoryInjectionEnabled
? "border border-blue-300 bg-blue-50 text-blue-700 ring-1 ring-blue-300/35 hover:bg-blue-100 dark:border-cyan-300/70 dark:bg-cyan-400/20 dark:text-cyan-50 dark:ring-cyan-300/35 dark:shadow-[0_0_0_1px_rgba(34,211,238,0.18),0_0_18px_rgba(34,211,238,0.18)] dark:hover:bg-cyan-400/25"
: compact
? "border border-transparent bg-muted/40 text-muted-foreground hover:bg-muted hover:text-foreground"
: "border-0 bg-transparent text-muted-foreground hover:bg-muted/70 hover:text-foreground dark:text-slate-400 dark:hover:bg-slate-700/70 dark:hover:text-slate-100",
)}
onClick={handleMemoryInjectionToggle}
>
{memoryInjectionEnabled && (
<CheckIcon className="size-3 text-blue-700 dark:text-cyan-50" />
)}
<BrainCircuitIcon className="size-3" />
<span className="text-xs font-normal">记忆</span>
</PromptInputButton>
</Tooltip>
<Tooltip
content={
@@ -1166,7 +1172,7 @@ export function InputBox({
className={cn(
"rounded-full border-border bg-background shadow-sm hover:bg-muted dark:bg-blue-500 dark:border-blue-500 dark:text-white dark:hover:bg-blue-400 dark:shadow-none",
compact &&
"size-7 rounded-lg border-0 bg-[var(--primary)] text-[var(--primary-foreground)] hover:bg-[color-mix(in_srgb,var(--primary)_88%,black)] disabled:bg-muted disabled:text-muted-foreground/40 dark:bg-[var(--primary)] dark:border-0 dark:text-[var(--primary-foreground)] dark:hover:bg-[color-mix(in_srgb,var(--primary)_88%,black)] dark:disabled:bg-muted",
"size-7 rounded-lg border-0 bg-[var(--primary)] text-[var(--primary-foreground)] hover:opacity-90 disabled:bg-muted disabled:text-muted-foreground/40 dark:bg-[var(--primary)] dark:border-0 dark:text-[var(--primary-foreground)] dark:hover:opacity-90 dark:disabled:bg-muted",
)}
disabled={disabled}
variant="outline"
@@ -112,7 +112,7 @@ export function RecentRoundtableList() {
}
try {
await rename(id, title);
toast.success("✏️ 已重命名");
toast.success("已重命名");
} catch {
toast.error("重命名失败,请重试");
} finally {
@@ -126,7 +126,7 @@ export function RecentRoundtableList() {
if (!window.confirm("确定删除这条会商记录吗?此操作不可恢复。")) return;
try {
await remove(draft.id);
toast.success("🗑 已删除会商记录");
toast.success("已删除会商记录");
} catch {
toast.error("删除失败,请重试");
}
@@ -8,7 +8,7 @@ CM Assistant is an internal super-agent platform that orchestrates sub-agents, m
---
## 🚀 Core Features
## Core Features
* **Skills & Tools**: With built-in and extensible skills and tools, CM Assistant can do almost anything.
* **Sub-Agents**: Sub-Agents help the main agent to do the tasks that are too complex to be done by the main agent.
@@ -1,12 +1,12 @@
"use client";
import { useMemo } from "react";
import { CopyIcon, LogOutIcon, UserIcon } from "lucide-react";
import { toast } from "sonner";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { clearStoredAuth, getAccountDisplayName, getStoredAuth } from "@/core/auth";
import { useLiveAccount } from "@/core/auth/use-live-account";
import { OptType, writeYLog } from "@/core/log/y-log";
import { clearYLogToken } from "@/core/log/y-log-token";
@@ -27,7 +27,10 @@ function copy(text: string, hint = "已复制") {
}
export function AccountSettingsPage() {
const auth = useMemo(() => getStoredAuth(), []);
// Subscribe to same-tab and cross-tab auth changes. The dialog may stay open
// while the active account is switched elsewhere.
useLiveAccount();
const auth = getStoredAuth();
if (!auth) {
return (
@@ -1,35 +1,28 @@
"use client";
import { User } from "lucide-react";
import { useMemo } from "react";
import { getStoredAuth } from "@/core/auth";
import { useLiveAccount } from "@/core/auth/use-live-account";
import { useMyPosition } from "@/core/positions";
import { cn } from "@/lib/utils";
import { readUserInfo } from "@/strategy-components/lib/light-app";
/**
* 名称 + 岗位 用户标识。全量模式由 strategy-components/Header.tsx 顶栏右侧展示;
* 紧凑模式(不带全局顶栏)无处承载,故抽成共享组件,复用同一套数据来源
* (`readUserInfo().userName` 优先、回退 auth 邮箱前缀;岗位取 `useMyPosition`),
* (实时 auth 展示名;岗位取 `useMyPosition`),
* 供页面级顶栏(如 ChatPage)右侧复用,保证两套布局一致。
*
* 配色按当前内容区前景色(foreground / muted-foreground),适配聊天页浅底顶栏,
* 而非 Header 的白字贴 banner 方案。
*/
export function UserIdentityBadge({ className }: { className?: string }) {
const auth = getStoredAuth();
// userInfo belongs to external-app route parameters and can legitimately
// outlive a login switch. The visible account name must instead match the
// settings page's current auth identity.
const liveAccount = useLiveAccount();
const { data: myPosition } = useMyPosition();
const positionName = myPosition?.name?.trim() || "";
// 与 Header.tsx 一致:外部登录写入的真实用户名优先,缺省回退邮箱前缀。
const displayName = useMemo(() => {
const info = readUserInfo();
const name = typeof info.userName === "string" ? info.userName.trim() : "";
if (name) return name;
const email = auth?.email?.trim();
return email ? email.split("@")[0] : "用户";
}, [auth?.email]);
const displayName = liveAccount.displayName;
return (
<div
@@ -1,16 +1,14 @@
"use client";
import { useSearchParams } from "next/navigation";
import { useEffect, useMemo } from "react";
import { useMemo } from "react";
import { useI18n } from "@/core/i18n/hooks";
import { cn } from "@/lib/utils";
import { AuroraText } from "../ui/aurora-text";
let waved = false;
export function Welcome({
export function Welcome({
className,
mode,
}: {
@@ -26,10 +24,7 @@ export function Welcome({
}
return ["var(--color-foreground)"];
}, [isUltra]);
useEffect(() => {
waved = true;
}, []);
return (
return (
<div
className={cn(
"mx-auto flex w-full flex-col items-center justify-center gap-2 px-8 py-4 text-center",
@@ -37,15 +32,12 @@ export function Welcome({
)}
>
<div className="text-[26px] font-medium tracking-tight text-foreground/85">
{searchParams.get("mode") === "skill" ? (
`✨ ${t.welcome.createYourOwnSkill} ✨`
) : (
<div className="flex items-center gap-2.5">
<div className={cn("inline-block text-2xl", !waved ? "animate-wave" : "")}>
{isUltra ? "🚀" : "👋"}
</div>
<AuroraText colors={colors}>{t.welcome.greeting}</AuroraText>
</div>
{searchParams.get("mode") === "skill" ? (
t.welcome.createYourOwnSkill
) : (
<div className="flex items-center">
<AuroraText colors={colors}>{t.welcome.greeting}</AuroraText>
</div>
)}
</div>
{searchParams.get("mode") === "skill" ? (
@@ -36,8 +36,8 @@ const NEW_CHAT_CLASS =
// 紧凑模式(CM 参考图):白卡描边、居中、主色 + 图标
const NEW_CHAT_COMPACT_CLASS =
"h-9 justify-center gap-2 rounded-xl bg-card text-[13px] font-semibold text-foreground transition-colors " +
"border border-[color-mix(in_srgb,var(--primary)_18%,transparent)] " +
"hover:bg-[color-mix(in_srgb,var(--primary)_4%,transparent)] hover:text-[var(--primary)] hover:border-[color-mix(in_srgb,var(--primary)_32%,transparent)] " +
"border border-[rgb(var(--legacy-primary-rgb)/18%)] " +
"hover:bg-[rgb(var(--legacy-primary-rgb)/4%)] hover:text-[var(--primary)] hover:border-[rgb(var(--legacy-primary-rgb)/32%)] " +
"data-[active=true]:bg-card data-[active=true]:text-[var(--primary)] data-[active=true]:shadow-none";
export function WorkspaceNavChatList({