mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-15 02:16:56 +00:00
fix(discord): qualify group titles across surfaces (#1422)
* fix(discord): qualify group titles across surfaces * feat(ui): add searchable delivery target selectors --------- Co-authored-by: Collective Developer <man@collective.dev>
This commit is contained in:
1 parent
0c1ededc92
commit
0d588e05c1
8 files changed
+75
-88
No files matched your search
@@ -193,7 +193,18 @@ export function Combobox({
|
||||
inputDirtyRef.current = true;
|
||||
setInputDirty(true);
|
||||
}
|
||||
if (!open && options.length > 0) setOpen(true);
|
||||
if (!open && (options.length > 0 || allowCustom)) setOpen(true);
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === "Escape") {
|
||||
setOpen(false);
|
||||
return;
|
||||
}
|
||||
if (e.key === "Enter" && allowCustom && isCustomValue) {
|
||||
e.preventDefault();
|
||||
handleSelect(search.trim());
|
||||
}
|
||||
};
|
||||
|
||||
const handleFocus = (e: React.FocusEvent) => {
|
||||
@@ -248,6 +259,7 @@ export function Combobox({
|
||||
ref={inputRef}
|
||||
value={search}
|
||||
onChange={handleInputChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
onFocus={handleFocus}
|
||||
disabled={disabled}
|
||||
placeholder={placeholder}
|
||||
|
||||
@@ -224,6 +224,7 @@
|
||||
"excludeGroups": "Excluded Discord channels",
|
||||
"excludeGroupsPlaceholder": "Select a channel to exclude",
|
||||
"manualExcludePlaceholder": "Paste Discord channel ID",
|
||||
"useDiscordChannelId": "Use Discord channel ID:",
|
||||
"addExcludedGroup": "Add",
|
||||
"noExcludeGroups": "No Discord channel history found yet.",
|
||||
"noExcludedGroups": "No channels excluded.",
|
||||
|
||||
@@ -223,6 +223,7 @@
|
||||
"excludeGroups": "Channel Discord loại trừ",
|
||||
"excludeGroupsPlaceholder": "Chọn channel cần loại trừ",
|
||||
"manualExcludePlaceholder": "Dán Discord channel ID",
|
||||
"useDiscordChannelId": "Dùng Discord channel ID:",
|
||||
"addExcludedGroup": "Thêm",
|
||||
"noExcludeGroups": "Chưa có lịch sử channel Discord.",
|
||||
"noExcludedGroups": "Chưa loại trừ channel nào.",
|
||||
|
||||
@@ -223,6 +223,7 @@
|
||||
"excludeGroups": "排除的 Discord 频道",
|
||||
"excludeGroupsPlaceholder": "选择要排除的频道",
|
||||
"manualExcludePlaceholder": "粘贴 Discord 频道 ID",
|
||||
"useDiscordChannelId": "使用 Discord 频道 ID:",
|
||||
"addExcludedGroup": "添加",
|
||||
"noExcludeGroups": "尚未发现 Discord 频道历史。",
|
||||
"noExcludedGroups": "尚未排除任何频道。",
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { Send } from "lucide-react";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Combobox } from "@/components/ui/combobox";
|
||||
import {
|
||||
Select, SelectContent, SelectItem, SelectTrigger, SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
@@ -69,19 +70,17 @@ export function HeartbeatDeliverySection({
|
||||
const filtered = targets.filter((tgt) => tgt.channel === channel);
|
||||
if (filtered.length > 0) {
|
||||
return (
|
||||
<Select value={chatId || "__none__"} onValueChange={(v) => setChatId(v === "__none__" ? "" : v)}>
|
||||
<SelectTrigger className="w-full text-base md:text-sm">
|
||||
<SelectValue placeholder={t("heartbeat.chatIdPlaceholder")} />
|
||||
</SelectTrigger>
|
||||
<SelectContent position="popper">
|
||||
<SelectItem value="__none__">{t("heartbeat.channelNone")}</SelectItem>
|
||||
{filtered.map((tgt) => (
|
||||
<SelectItem key={tgt.chatId} value={tgt.chatId}>
|
||||
{tgt.title ? `${tgt.title} (${tgt.chatId})` : tgt.chatId}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Combobox
|
||||
value={chatId}
|
||||
onChange={setChatId}
|
||||
options={filtered.map((target) => ({
|
||||
value: target.chatId,
|
||||
label: target.title ? `${target.title} (${target.chatId})` : target.chatId,
|
||||
}))}
|
||||
placeholder={t("heartbeat.chatIdPlaceholder")}
|
||||
allowCustom
|
||||
className="w-full text-base md:text-sm"
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
|
||||
@@ -4,13 +4,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Pagination } from "@/components/shared/pagination";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Combobox } from "@/components/ui/combobox";
|
||||
import type { ChannelMemoryConfig } from "@/types/channel";
|
||||
import { useChannelMemoryExtraction } from "../hooks/use-channel-memory-extraction";
|
||||
import {
|
||||
@@ -99,13 +93,7 @@ export function PassiveMemorySection({ instanceId, channelType }: PassiveMemoryS
|
||||
}, [status?.config]);
|
||||
|
||||
useEffect(() => {
|
||||
if (promptGroupOptions.length === 0) {
|
||||
setSelectedPromptHistoryKey("");
|
||||
return;
|
||||
}
|
||||
if (selectedPromptHistoryKey && promptGroupOptions.some((group) => group.history_key === selectedPromptHistoryKey)) {
|
||||
return;
|
||||
}
|
||||
if (selectedPromptHistoryKey) return;
|
||||
setSelectedPromptHistoryKey(promptGroupOptions[0]?.history_key ?? "");
|
||||
}, [promptGroupOptions, selectedPromptHistoryKey]);
|
||||
|
||||
@@ -298,22 +286,18 @@ export function PassiveMemorySection({ instanceId, channelType }: PassiveMemoryS
|
||||
<div className="text-xs font-medium text-muted-foreground">
|
||||
{t("detail.passiveMemory.groupCustomPrompt")}
|
||||
</div>
|
||||
<Select
|
||||
<Combobox
|
||||
value={selectedPromptHistoryKey}
|
||||
onValueChange={setSelectedPromptHistoryKey}
|
||||
disabled={promptGroupOptions.length === 0}
|
||||
>
|
||||
<SelectTrigger size="sm">
|
||||
<SelectValue placeholder={t("detail.passiveMemory.groupCustomPromptPlaceholder")} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{promptGroupOptions.map((group) => (
|
||||
<SelectItem key={group.history_key} value={group.history_key}>
|
||||
{formatPromptGroupLabel(group)}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
onChange={() => undefined}
|
||||
onSelect={setSelectedPromptHistoryKey}
|
||||
options={promptGroupOptions.map((group) => ({
|
||||
value: group.history_key,
|
||||
label: formatPromptGroupLabel(group),
|
||||
}))}
|
||||
placeholder={t("detail.passiveMemory.groupCustomPromptPlaceholder")}
|
||||
allowCustom
|
||||
customLabel={t("detail.passiveMemory.useDiscordChannelId")}
|
||||
/>
|
||||
{selectedPromptHistoryKey ? (
|
||||
<TextareaBlock
|
||||
label={selectedPromptGroup ? formatPromptGroupLabel(selectedPromptGroup) : selectedPromptHistoryKey}
|
||||
@@ -330,22 +314,18 @@ export function PassiveMemorySection({ instanceId, channelType }: PassiveMemoryS
|
||||
<div className="text-xs font-medium text-muted-foreground">
|
||||
{t("detail.passiveMemory.excludeGroups")}
|
||||
</div>
|
||||
<Select
|
||||
<Combobox
|
||||
value=""
|
||||
onValueChange={addExcludedHistoryKey}
|
||||
disabled={availableExcludeGroupOptions.length === 0}
|
||||
>
|
||||
<SelectTrigger size="sm">
|
||||
<SelectValue placeholder={t("detail.passiveMemory.excludeGroupsPlaceholder")} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{availableExcludeGroupOptions.map((group) => (
|
||||
<SelectItem key={group.history_key} value={group.history_key}>
|
||||
{formatPromptGroupLabel(group)}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
onChange={() => undefined}
|
||||
onSelect={addExcludedHistoryKey}
|
||||
options={availableExcludeGroupOptions.map((group) => ({
|
||||
value: group.history_key,
|
||||
label: formatPromptGroupLabel(group),
|
||||
}))}
|
||||
placeholder={t("detail.passiveMemory.excludeGroupsPlaceholder")}
|
||||
allowCustom
|
||||
customLabel={t("detail.passiveMemory.useDiscordChannelId")}
|
||||
/>
|
||||
{excludedGroupOptions.length > 0 ? (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{excludedGroupOptions.map((group) => {
|
||||
|
||||
@@ -207,22 +207,17 @@ export function CronAdvancedDialog({ open, onOpenChange, job, onUpdate }: CronAd
|
||||
const filtered = targets.filter((tgt) => tgt.channel === channel);
|
||||
if (filtered.length > 0) {
|
||||
return (
|
||||
<Select
|
||||
value={to || "__none__"}
|
||||
onValueChange={(v) => setValue("to", v === "__none__" ? "" : v)}
|
||||
>
|
||||
<SelectTrigger className="text-base md:text-sm">
|
||||
<SelectValue placeholder={t("detail.toPlaceholder")} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="__none__">{t("detail.toPlaceholder")}</SelectItem>
|
||||
{filtered.map((tgt) => (
|
||||
<SelectItem key={tgt.chatId} value={tgt.chatId} title={tgt.title ? `${tgt.title} (${tgt.chatId})` : tgt.chatId}>
|
||||
<span className="truncate">{tgt.title ? `${tgt.title} (${tgt.chatId})` : tgt.chatId}</span>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Combobox
|
||||
value={to}
|
||||
onChange={(value) => setValue("to", value)}
|
||||
options={filtered.map((target) => ({
|
||||
value: target.chatId,
|
||||
label: target.title ? `${target.title} (${target.chatId})` : target.chatId,
|
||||
}))}
|
||||
placeholder={t("detail.toPlaceholder")}
|
||||
allowCustom
|
||||
className="text-base md:text-sm"
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Combobox } from "@/components/ui/combobox";
|
||||
|
||||
export interface DeliveryTarget {
|
||||
channel: string;
|
||||
@@ -85,20 +86,17 @@ export function CronDeliverySection({
|
||||
const filtered = targets.filter((tgt) => tgt.channel === channel);
|
||||
if (filtered.length > 0) {
|
||||
return (
|
||||
<Select value={to || "__none__"} onValueChange={(v) => setTo(v === "__none__" ? "" : v)}>
|
||||
<SelectTrigger className="text-base md:text-sm">
|
||||
<SelectValue placeholder={t("detail.toPlaceholder")} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="__none__">{t("detail.toPlaceholder")}</SelectItem>
|
||||
{filtered.map((tgt) => (
|
||||
<SelectItem key={tgt.chatId} value={tgt.chatId}
|
||||
title={tgt.title ? `${tgt.title} (${tgt.chatId})` : tgt.chatId}>
|
||||
<span className="truncate">{tgt.title ? `${tgt.title} (${tgt.chatId})` : tgt.chatId}</span>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Combobox
|
||||
value={to}
|
||||
onChange={setTo}
|
||||
options={filtered.map((target) => ({
|
||||
value: target.chatId,
|
||||
label: target.title ? `${target.title} (${target.chatId})` : target.chatId,
|
||||
}))}
|
||||
placeholder={t("detail.toPlaceholder")}
|
||||
allowCustom
|
||||
className="text-base md:text-sm"
|
||||
/>
|
||||
);
|
||||
}
|
||||
return <Input value={to} onChange={(e) => setTo(e.target.value)}
|
||||
|
||||
Reference in new issue
Block a user