fixes
This commit is contained in:
@@ -25,11 +25,11 @@ function MessageBubble({ msg, index, authorAvatar, authorName, onReport, isFaile
|
|||||||
window.open(`https://duckduckgo.com/?q=${encodeURIComponent(msg.content.slice(0, 400))}`, "_blank", "noopener,noreferrer");
|
window.open(`https://duckduckgo.com/?q=${encodeURIComponent(msg.content.slice(0, 400))}`, "_blank", "noopener,noreferrer");
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className={cn("flex gap-3", isUser && "flex-row-reverse")}>
|
<div className={cn("flex gap-2 sm:gap-3", isUser && "flex-row-reverse")}>
|
||||||
{isUser
|
{isUser
|
||||||
? <Avatar name={authorName} url={authorAvatar} size={32} />
|
? <Avatar name={authorName} url={authorAvatar} size={32} />
|
||||||
: <AssistantAvatar size={32} />}
|
: <AssistantAvatar size={32} />}
|
||||||
<div className={cn("min-w-0 max-w-[85%]", isUser && "flex flex-col items-end")}>
|
<div className={cn("min-w-0 max-w-[88%] sm:max-w-[85%]", isUser && "flex flex-col items-end")}>
|
||||||
<div className="mb-1 flex items-center gap-1.5 text-xs opacity-60">
|
<div className="mb-1 flex items-center gap-1.5 text-xs opacity-60">
|
||||||
{isUser ? <UserIcon size={12} /> : <Bot size={12} />}
|
{isUser ? <UserIcon size={12} /> : <Bot size={12} />}
|
||||||
{isUser ? t("chat.you") : "KisAssistant"}
|
{isUser ? t("chat.you") : "KisAssistant"}
|
||||||
@@ -39,7 +39,7 @@ function MessageBubble({ msg, index, authorAvatar, authorName, onReport, isFaile
|
|||||||
<UserMessageContent content={msg.content} />
|
<UserMessageContent content={msg.content} />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="rounded-3xl rounded-tl-lg border border-stone-200/70 bg-white px-5 py-3.5 shadow-sm dark:border-zinc-800 dark:bg-zinc-900">
|
<div className="rounded-3xl rounded-tl-lg border border-stone-200/70 bg-white px-4 py-3 shadow-sm sm:px-5 sm:py-3.5 dark:border-zinc-800 dark:bg-zinc-900">
|
||||||
<Markdown text={msg.content} />
|
<Markdown text={msg.content} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -313,8 +313,8 @@ export function Chat({
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div ref={boxRef} className="flex-1 space-y-5 overflow-y-auto px-4 py-6 sm:px-8">
|
<div ref={boxRef} className="flex-1 space-y-4 overflow-y-auto px-4 py-4 sm:space-y-5 sm:px-8 sm:py-6">
|
||||||
<div className="mx-auto w-full max-w-3xl space-y-5">
|
<div className="mx-auto w-full max-w-3xl space-y-4 sm:space-y-5">
|
||||||
{messages.length === 0 && !streaming && (
|
{messages.length === 0 && !streaming && (
|
||||||
<div className="py-16 text-center">
|
<div className="py-16 text-center">
|
||||||
<FlowerMark size={56} dynamic={false} className="mx-auto mb-4" />
|
<FlowerMark size={56} dynamic={false} className="mx-auto mb-4" />
|
||||||
@@ -326,11 +326,11 @@ export function Chat({
|
|||||||
<MessageBubble key={i} msg={m} index={i} authorAvatar={user.avatarUrl} authorName={user.displayName} onReport={readOnly ? undefined : setReportIndex} isFailed={failedIndex === i} retryCount={retryCount} maxRetries={maxRetries} retryDisabled={sending} onRetry={failedIndex === i && retryCount < maxRetries ? onRetry : undefined} />
|
<MessageBubble key={i} msg={m} index={i} authorAvatar={user.avatarUrl} authorName={user.displayName} onReport={readOnly ? undefined : setReportIndex} isFailed={failedIndex === i} retryCount={retryCount} maxRetries={maxRetries} retryDisabled={sending} onRetry={failedIndex === i && retryCount < maxRetries ? onRetry : undefined} />
|
||||||
))}
|
))}
|
||||||
{streaming !== "" && (
|
{streaming !== "" && (
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-2 sm:gap-3">
|
||||||
<AssistantAvatar size={32} />
|
<AssistantAvatar size={32} />
|
||||||
<div className="min-w-0 max-w-[85%]">
|
<div className="min-w-0 max-w-[88%] sm:max-w-[85%]">
|
||||||
<div className="mb-1 text-xs opacity-60">KisAssistant</div>
|
<div className="mb-1 text-xs opacity-60">KisAssistant</div>
|
||||||
<div className="rounded-3xl rounded-tl-lg border border-stone-200/70 bg-white px-5 py-3.5 shadow-sm dark:border-zinc-800 dark:bg-zinc-900">
|
<div className="rounded-3xl rounded-tl-lg border border-stone-200/70 bg-white px-4 py-3 shadow-sm sm:px-5 sm:py-3.5 dark:border-zinc-800 dark:bg-zinc-900">
|
||||||
<div className="typing-cursor"><Markdown text={streaming} /></div>
|
<div className="typing-cursor"><Markdown text={streaming} /></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -36,6 +36,15 @@ export function Markdown({ text }: { text: string }) {
|
|||||||
code({ children }) {
|
code({ children }) {
|
||||||
return <code>{children}</code>;
|
return <code>{children}</code>;
|
||||||
},
|
},
|
||||||
|
table({ children }) {
|
||||||
|
// Wide tables would blow the bubble off-screen on mobile —
|
||||||
|
// scroll them inside their own pane instead.
|
||||||
|
return (
|
||||||
|
<div className="overflow-x-auto pb-1">
|
||||||
|
<table>{children}</table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{text}
|
{text}
|
||||||
|
|||||||
@@ -402,6 +402,11 @@ export function SettingsModal({
|
|||||||
<input
|
<input
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.target.value)}
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
|
// The search field lives inside the settings <form>: Enter must
|
||||||
|
// only filter, never submit (save + close the modal).
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Enter") e.preventDefault();
|
||||||
|
}}
|
||||||
placeholder={t("settings.searchPh")}
|
placeholder={t("settings.searchPh")}
|
||||||
aria-label={t("settings.searchAria")}
|
aria-label={t("settings.searchAria")}
|
||||||
className="w-full rounded-2xl border border-stone-200/70 bg-stone-100 py-2 pl-9 pr-8 text-sm outline-none transition placeholder:text-stone-400 focus:border-accent-500/60 focus:bg-white dark:border-zinc-800 dark:bg-zinc-800/60 dark:placeholder:text-zinc-500 dark:focus:bg-zinc-900"
|
className="w-full rounded-2xl border border-stone-200/70 bg-stone-100 py-2 pl-9 pr-8 text-sm outline-none transition placeholder:text-stone-400 focus:border-accent-500/60 focus:bg-white dark:border-zinc-800 dark:bg-zinc-800/60 dark:placeholder:text-zinc-500 dark:focus:bg-zinc-900"
|
||||||
@@ -419,7 +424,16 @@ export function SettingsModal({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{search.trim() && visibleCats.length === 0 ? (
|
{search.trim() && visibleCats.length === 0 ? (
|
||||||
<p className="px-2 py-4 text-center text-sm opacity-50">{t("settings.noMatch", { q: search.trim() })}</p>
|
<div className="space-y-2 px-2 py-4 text-center">
|
||||||
|
<p className="text-sm opacity-50">{t("settings.noMatch", { q: search.trim() })}</p>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setSearch("")}
|
||||||
|
className="rounded-full border border-stone-200 bg-white px-3.5 py-1.5 text-sm transition hover:bg-stone-50 dark:border-zinc-700 dark:bg-zinc-900 dark:hover:bg-zinc-800"
|
||||||
|
>
|
||||||
|
{t("settings.clearSearch")}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col gap-4 sm:flex-row">
|
<div className="flex flex-col gap-4 sm:flex-row">
|
||||||
<nav aria-label="Settings categories" className="flex shrink-0 gap-1.5 overflow-x-auto sm:w-44 sm:flex-col">
|
<nav aria-label="Settings categories" className="flex shrink-0 gap-1.5 overflow-x-auto sm:w-44 sm:flex-col">
|
||||||
@@ -427,7 +441,10 @@ export function SettingsModal({
|
|||||||
<button
|
<button
|
||||||
key={c.id}
|
key={c.id}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setCat(c.id)}
|
// Picking a category exits search mode: keeping the query
|
||||||
|
// would leave the nav filtered (often down to this single
|
||||||
|
// button, or empty) and trap the user in the search view.
|
||||||
|
onClick={() => { setCat(c.id); setSearch(""); }}
|
||||||
aria-current={activeCat === c.id}
|
aria-current={activeCat === c.id}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex shrink-0 items-center gap-2.5 rounded-2xl px-3.5 py-2.5 text-sm transition",
|
"flex shrink-0 items-center gap-2.5 rounded-2xl px-3.5 py-2.5 text-sm transition",
|
||||||
|
|||||||
@@ -465,7 +465,7 @@ const GRADIENTS = [
|
|||||||
export function Avatar({ name, url, size = 36 }: { name: string; url?: string; size?: number }) {
|
export function Avatar({ name, url, size = 36 }: { name: string; url?: string; size?: number }) {
|
||||||
const cdnV = useCdnVersion();
|
const cdnV = useCdnVersion();
|
||||||
if (url) {
|
if (url) {
|
||||||
return <img src={withCdnVersion(url, cdnV)} alt={name} width={size} height={size} className="rounded-full object-cover" style={{ width: size, height: size }} />;
|
return <img src={withCdnVersion(url, cdnV)} alt={name} width={size} height={size} className="shrink-0 rounded-full object-cover" style={{ width: size, height: size }} />;
|
||||||
}
|
}
|
||||||
const initial = (name.trim()[0] || "?").toUpperCase();
|
const initial = (name.trim()[0] || "?").toUpperCase();
|
||||||
const g = GRADIENTS[(name.charCodeAt(0) || 0) % GRADIENTS.length];
|
const g = GRADIENTS[(name.charCodeAt(0) || 0) % GRADIENTS.length];
|
||||||
|
|||||||
Reference in New Issue
Block a user