Add read-only production runtime audit
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { Send, Square, Sparkles, Pill, Search, Command } from "lucide-react";
|
||||
import type { MonographPickerState } from "@duoc-thu/shared-types";
|
||||
import { Send, Square, Sparkles, Pill, Search, Command, X, BookOpen } from "lucide-react";
|
||||
import { cn } from "@duoc-thu/ui";
|
||||
|
||||
interface ComposerProps {
|
||||
@@ -9,6 +10,12 @@ interface ComposerProps {
|
||||
isLoading?: boolean;
|
||||
onStop?: () => void;
|
||||
initialValue?: string;
|
||||
monographPicker?: MonographPickerState | null;
|
||||
onToggleSection?: (sectionKey: string) => void;
|
||||
onClearMonograph?: () => void;
|
||||
onSubmitMonograph?: () => void;
|
||||
responseMode?: "ai" | "monograph";
|
||||
onResponseModeChange?: (mode: "ai" | "monograph") => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
@@ -17,6 +24,12 @@ export function Composer({
|
||||
isLoading = false,
|
||||
onStop,
|
||||
initialValue = "",
|
||||
monographPicker,
|
||||
onToggleSection,
|
||||
onClearMonograph,
|
||||
onSubmitMonograph,
|
||||
responseMode = "ai",
|
||||
onResponseModeChange,
|
||||
className,
|
||||
}: ComposerProps) {
|
||||
const [value, setValue] = useState(initialValue);
|
||||
@@ -103,7 +116,12 @@ export function Composer({
|
||||
|
||||
const handleSubmit = () => {
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed || isLoading) return;
|
||||
if (isLoading) return;
|
||||
if (!trimmed && monographPicker) {
|
||||
onSubmitMonograph?.();
|
||||
return;
|
||||
}
|
||||
if (!trimmed) return;
|
||||
onSubmit(trimmed);
|
||||
setValue("");
|
||||
setSuggestions([]);
|
||||
@@ -181,6 +199,36 @@ export function Composer({
|
||||
|
||||
{/* Main Composer Box */}
|
||||
<div className="relative flex flex-col rounded-3xl border border-border-subtle bg-surface p-2 shadow-surface transition-all focus-within:border-border-accent focus-within:shadow-elevated glass-panel">
|
||||
{monographPicker && (
|
||||
<div className="flex flex-wrap items-center gap-2 px-2 pt-1 pb-2 border-b border-border-subtle/50">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClearMonograph}
|
||||
className="inline-flex items-center gap-1.5 rounded-full bg-accent-primary px-3 py-1.5 text-xs font-bold text-txt-inverse"
|
||||
>
|
||||
<Pill className="h-3.5 w-3.5" />
|
||||
{monographPicker.drugName}
|
||||
<X className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
{monographPicker.selectedSectionKeys.map((sectionKey) => {
|
||||
const section = monographPicker.sections.find(
|
||||
(item) => item.sectionKey === sectionKey
|
||||
);
|
||||
if (!section) return null;
|
||||
return (
|
||||
<button
|
||||
key={sectionKey}
|
||||
type="button"
|
||||
onClick={() => onToggleSection?.(sectionKey)}
|
||||
className="inline-flex items-center gap-1.5 rounded-full border border-border-accent bg-accent-soft px-3 py-1.5 text-xs font-semibold text-accent-primary"
|
||||
>
|
||||
{section.sectionTitle}
|
||||
<X className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
<textarea
|
||||
ref={inputRef}
|
||||
value={value}
|
||||
@@ -192,9 +240,33 @@ export function Composer({
|
||||
/>
|
||||
|
||||
<div className="flex items-center justify-between gap-2 pt-2 px-2 border-t border-border-subtle/50">
|
||||
<div className="flex items-center gap-1.5 text-[0.7rem] text-txt-muted">
|
||||
<Command className="w-3 h-3" />
|
||||
<span className="hidden sm:inline">Nhấn Enter để gửi • Shift+Enter để xuống dòng</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
onResponseModeChange?.(
|
||||
responseMode === "ai" ? "monograph" : "ai"
|
||||
)
|
||||
}
|
||||
className={cn(
|
||||
"inline-flex items-center gap-1.5 rounded-xl border px-2.5 py-1.5 text-[0.7rem] font-bold transition-colors",
|
||||
responseMode === "monograph"
|
||||
? "border-border-accent bg-accent-soft text-accent-primary"
|
||||
: "border-border-subtle bg-surface-elevated text-txt-secondary hover:text-accent-primary"
|
||||
)}
|
||||
title="Chuyển giữa AI tổng hợp và tra nguyên văn chuyên luận"
|
||||
>
|
||||
{responseMode === "ai" ? (
|
||||
<Sparkles className="h-3.5 w-3.5" />
|
||||
) : (
|
||||
<BookOpen className="h-3.5 w-3.5" />
|
||||
)}
|
||||
{responseMode === "ai" ? "AI tổng hợp" : "Chuyên luận"}
|
||||
</button>
|
||||
<div className="hidden items-center gap-1.5 text-[0.7rem] text-txt-muted sm:flex">
|
||||
<Command className="w-3 h-3" />
|
||||
<span>Enter để gửi • Shift+Enter để xuống dòng</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -210,11 +282,11 @@ export function Composer({
|
||||
) : (
|
||||
<button
|
||||
onClick={handleSubmit}
|
||||
disabled={!value.trim()}
|
||||
disabled={!value.trim() && !monographPicker}
|
||||
type="button"
|
||||
className={cn(
|
||||
"flex items-center gap-1.5 px-4 py-1.5 rounded-xl text-xs font-bold transition-all shadow-sm",
|
||||
value.trim()
|
||||
value.trim() || monographPicker
|
||||
? "bg-accent-primary text-txt-inverse hover:bg-accent-hover"
|
||||
: "bg-surface-elevated text-txt-muted cursor-not-allowed"
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user