Spaces:
Runtime error
Runtime error
Đào Minh Tú Copilot commited on
Commit ·
adb9e89
1
Parent(s): ca0e286
feat(composer): default AI toggles ON + toast feedback on click
Browse files- Self-critique and auto-compact-context now default to ON for new
visitors (first-time = no localStorage entry write 'true' so it
sticks; existing users keep their previous choice).
- Clicking either icon now fires a sonner toast describing what just
changed and what the feature does, so users actually know what those
little icons under the input do.
Adds 'sonner' dep + <Toaster /> in app/layout.tsx (bottom-center, rich
colors, theme-aware, dismissible).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- app/layout.tsx +2 -0
- components/composer.tsx +51 -6
- package-lock.json +11 -0
- package.json +1 -0
app/layout.tsx
CHANGED
|
@@ -6,6 +6,7 @@ import { ThemeProvider } from "@/components/theme-provider"
|
|
| 6 |
import { AuthProvider } from "@/components/auth-provider"
|
| 7 |
import { PWARegister } from "@/components/pwa-register"
|
| 8 |
import { VoiceMode } from "@/components/voice-mode"
|
|
|
|
| 9 |
import "./globals.css"
|
| 10 |
|
| 11 |
export const metadata: Metadata = {
|
|
@@ -38,6 +39,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
|
| 38 |
<TooltipProvider delayDuration={300}>
|
| 39 |
{children}
|
| 40 |
<VoiceMode />
|
|
|
|
| 41 |
</TooltipProvider>
|
| 42 |
</ThemeProvider>
|
| 43 |
</AuthProvider>
|
|
|
|
| 6 |
import { AuthProvider } from "@/components/auth-provider"
|
| 7 |
import { PWARegister } from "@/components/pwa-register"
|
| 8 |
import { VoiceMode } from "@/components/voice-mode"
|
| 9 |
+
import { Toaster } from "sonner"
|
| 10 |
import "./globals.css"
|
| 11 |
|
| 12 |
export const metadata: Metadata = {
|
|
|
|
| 39 |
<TooltipProvider delayDuration={300}>
|
| 40 |
{children}
|
| 41 |
<VoiceMode />
|
| 42 |
+
<Toaster position="bottom-center" theme="system" richColors closeButton />
|
| 43 |
</TooltipProvider>
|
| 44 |
</ThemeProvider>
|
| 45 |
</AuthProvider>
|
components/composer.tsx
CHANGED
|
@@ -18,6 +18,7 @@ import { searchActiveCollection } from "@/lib/collections"
|
|
| 18 |
import { countTokens } from "@/lib/tokens"
|
| 19 |
import { useSpeechRecognition } from "@/hooks/use-voice"
|
| 20 |
import { cn } from "@/lib/utils"
|
|
|
|
| 21 |
|
| 22 |
type QuoteEventDetail = {
|
| 23 |
text?: string
|
|
@@ -571,8 +572,22 @@ export function Composer({ value, onChange, onSubmit, onStop, isStreaming, disab
|
|
| 571 |
</div>
|
| 572 |
</div>
|
| 573 |
<div className="mt-1.5 flex items-center justify-end gap-1 px-2 text-[10px] text-muted-foreground">
|
| 574 |
-
<AiFeatureToggle
|
| 575 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 576 |
<span className="mx-1 h-3 w-px bg-border/60" aria-hidden />
|
| 577 |
<Tooltip>
|
| 578 |
<TooltipTrigger asChild>
|
|
@@ -616,16 +631,46 @@ export function Composer({ value, onChange, onSubmit, onStop, isStreaming, disab
|
|
| 616 |
)
|
| 617 |
}
|
| 618 |
|
| 619 |
-
function AiFeatureToggle({
|
| 620 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 621 |
useEffect(() => {
|
| 622 |
if (typeof window === "undefined") return
|
| 623 |
-
|
| 624 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 625 |
function toggle() {
|
| 626 |
const next = !enabled
|
| 627 |
setEnabled(next)
|
| 628 |
if (typeof window !== "undefined") window.localStorage.setItem(storageKey, String(next))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 629 |
}
|
| 630 |
return (
|
| 631 |
<Tooltip>
|
|
|
|
| 18 |
import { countTokens } from "@/lib/tokens"
|
| 19 |
import { useSpeechRecognition } from "@/hooks/use-voice"
|
| 20 |
import { cn } from "@/lib/utils"
|
| 21 |
+
import { toast } from "sonner"
|
| 22 |
|
| 23 |
type QuoteEventDetail = {
|
| 24 |
text?: string
|
|
|
|
| 572 |
</div>
|
| 573 |
</div>
|
| 574 |
<div className="mt-1.5 flex items-center justify-end gap-1 px-2 text-[10px] text-muted-foreground">
|
| 575 |
+
<AiFeatureToggle
|
| 576 |
+
storageKey="tans:self-critique"
|
| 577 |
+
label="Tự đánh giá câu trả lời"
|
| 578 |
+
icon={<Brain className="h-3.5 w-3.5" />}
|
| 579 |
+
defaultEnabled
|
| 580 |
+
onMessage="AI sẽ tự kiểm tra câu trả lời trước khi trả về để giảm sai sót."
|
| 581 |
+
offMessage="AI trả lời thẳng, không tự kiểm tra lại."
|
| 582 |
+
/>
|
| 583 |
+
<AiFeatureToggle
|
| 584 |
+
storageKey="tans:auto-compact"
|
| 585 |
+
label="Tự nén context khi đầy"
|
| 586 |
+
icon={<FolderArchive className="h-3.5 w-3.5" />}
|
| 587 |
+
defaultEnabled
|
| 588 |
+
onMessage="Khi context gần đầy, các tin nhắn cũ sẽ được tóm tắt tự động để chừa chỗ cho phần mới."
|
| 589 |
+
offMessage="Context sẽ không được nén — bạn cần chủ động xóa hoặc tạo chat mới khi đầy."
|
| 590 |
+
/>
|
| 591 |
<span className="mx-1 h-3 w-px bg-border/60" aria-hidden />
|
| 592 |
<Tooltip>
|
| 593 |
<TooltipTrigger asChild>
|
|
|
|
| 631 |
)
|
| 632 |
}
|
| 633 |
|
| 634 |
+
function AiFeatureToggle({
|
| 635 |
+
storageKey,
|
| 636 |
+
label,
|
| 637 |
+
icon,
|
| 638 |
+
defaultEnabled = false,
|
| 639 |
+
onMessage,
|
| 640 |
+
offMessage,
|
| 641 |
+
}: {
|
| 642 |
+
storageKey: string
|
| 643 |
+
label: string
|
| 644 |
+
icon: React.ReactNode
|
| 645 |
+
defaultEnabled?: boolean
|
| 646 |
+
onMessage?: string
|
| 647 |
+
offMessage?: string
|
| 648 |
+
}) {
|
| 649 |
+
const [enabled, setEnabled] = useState(defaultEnabled)
|
| 650 |
useEffect(() => {
|
| 651 |
if (typeof window === "undefined") return
|
| 652 |
+
const raw = window.localStorage.getItem(storageKey)
|
| 653 |
+
if (raw === null) {
|
| 654 |
+
// First-time visitor — apply default + persist so it sticks.
|
| 655 |
+
setEnabled(defaultEnabled)
|
| 656 |
+
window.localStorage.setItem(storageKey, String(defaultEnabled))
|
| 657 |
+
} else {
|
| 658 |
+
setEnabled(raw === "true")
|
| 659 |
+
}
|
| 660 |
+
}, [storageKey, defaultEnabled])
|
| 661 |
function toggle() {
|
| 662 |
const next = !enabled
|
| 663 |
setEnabled(next)
|
| 664 |
if (typeof window !== "undefined") window.localStorage.setItem(storageKey, String(next))
|
| 665 |
+
if (next) {
|
| 666 |
+
toast.success(`${label}: Bật`, {
|
| 667 |
+
description: onMessage ?? "Đã bật tính năng.",
|
| 668 |
+
})
|
| 669 |
+
} else {
|
| 670 |
+
toast(`${label}: Tắt`, {
|
| 671 |
+
description: offMessage ?? "Đã tắt tính năng.",
|
| 672 |
+
})
|
| 673 |
+
}
|
| 674 |
}
|
| 675 |
return (
|
| 676 |
<Tooltip>
|
package-lock.json
CHANGED
|
@@ -40,6 +40,7 @@
|
|
| 40 |
"rehype-katex": "^7.0.1",
|
| 41 |
"remark-gfm": "^4.0.0",
|
| 42 |
"remark-math": "^6.0.0",
|
|
|
|
| 43 |
"tailwind-merge": "^2.5.4",
|
| 44 |
"zod": "^3.23.8"
|
| 45 |
},
|
|
@@ -8706,6 +8707,16 @@
|
|
| 8706 |
"url": "https://github.com/sponsors/ljharb"
|
| 8707 |
}
|
| 8708 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8709 |
"node_modules/source-map-js": {
|
| 8710 |
"version": "1.2.1",
|
| 8711 |
"license": "BSD-3-Clause",
|
|
|
|
| 40 |
"rehype-katex": "^7.0.1",
|
| 41 |
"remark-gfm": "^4.0.0",
|
| 42 |
"remark-math": "^6.0.0",
|
| 43 |
+
"sonner": "^2.0.7",
|
| 44 |
"tailwind-merge": "^2.5.4",
|
| 45 |
"zod": "^3.23.8"
|
| 46 |
},
|
|
|
|
| 8707 |
"url": "https://github.com/sponsors/ljharb"
|
| 8708 |
}
|
| 8709 |
},
|
| 8710 |
+
"node_modules/sonner": {
|
| 8711 |
+
"version": "2.0.7",
|
| 8712 |
+
"resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz",
|
| 8713 |
+
"integrity": "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==",
|
| 8714 |
+
"license": "MIT",
|
| 8715 |
+
"peerDependencies": {
|
| 8716 |
+
"react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc",
|
| 8717 |
+
"react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc"
|
| 8718 |
+
}
|
| 8719 |
+
},
|
| 8720 |
"node_modules/source-map-js": {
|
| 8721 |
"version": "1.2.1",
|
| 8722 |
"license": "BSD-3-Clause",
|
package.json
CHANGED
|
@@ -41,6 +41,7 @@
|
|
| 41 |
"rehype-katex": "^7.0.1",
|
| 42 |
"remark-gfm": "^4.0.0",
|
| 43 |
"remark-math": "^6.0.0",
|
|
|
|
| 44 |
"tailwind-merge": "^2.5.4",
|
| 45 |
"zod": "^3.23.8"
|
| 46 |
},
|
|
|
|
| 41 |
"rehype-katex": "^7.0.1",
|
| 42 |
"remark-gfm": "^4.0.0",
|
| 43 |
"remark-math": "^6.0.0",
|
| 44 |
+
"sonner": "^2.0.7",
|
| 45 |
"tailwind-merge": "^2.5.4",
|
| 46 |
"zod": "^3.23.8"
|
| 47 |
},
|