Skip to content

Commit 5f0e2ad

Browse files
committed
Add Codemirror support and enhance API for binding updates
- Integrated Codemirror dependencies for improved code editing capabilities. - Added new UpdateBindingRequest and UpdateBindingResponse interfaces to the API for updating existing bindings. - Implemented updateBinding function in the API to handle binding updates, including validation and persistence to the configuration file. - Introduced a TagInput component for managing channel IDs and allowed users in the settings interface. - Enhanced the Settings component to support editing and updating bindings with the new TagInput functionality.
1 parent 85e1b06 commit 5f0e2ad

7 files changed

Lines changed: 812 additions & 36 deletions

File tree

interface/bun.lock

Lines changed: 43 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

interface/package.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@
99
"preview": "vite preview"
1010
},
1111
"dependencies": {
12+
"@codemirror/language": "^6.12.1",
13+
"@codemirror/legacy-modes": "^6.5.2",
14+
"@codemirror/state": "^6.5.4",
15+
"@codemirror/theme-one-dark": "^6.1.3",
16+
"@codemirror/view": "^6.39.14",
1217
"@fontsource/ibm-plex-sans": "^5.1.0",
1318
"@fortawesome/fontawesome-svg-core": "^7.2.0",
1419
"@fortawesome/free-brands-svg-icons": "^7.2.0",
@@ -35,6 +40,7 @@
3540
"@tanstack/router-devtools": "^1.159.5",
3641
"class-variance-authority": "^0.7.1",
3742
"clsx": "^2.1.1",
43+
"codemirror": "^6.0.2",
3844
"framer-motion": "^12.34.0",
3945
"graphology": "^0.26.0",
4046
"graphology-layout-forceatlas2": "^0.10.1",
@@ -46,6 +52,7 @@
4652
"recharts": "^3.7.0",
4753
"remark-gfm": "^4.0.1",
4854
"sigma": "^3.0.2",
55+
"smol-toml": "^1.6.0",
4956
"sonner": "^2.0.7",
5057
"zod": "^4.3.6"
5158
},

interface/src/api/client.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -738,6 +738,26 @@ export interface CreateBindingResponse {
738738
message: string;
739739
}
740740

741+
export interface UpdateBindingRequest {
742+
original_agent_id: string;
743+
original_channel: string;
744+
original_guild_id?: string;
745+
original_workspace_id?: string;
746+
original_chat_id?: string;
747+
agent_id: string;
748+
channel: string;
749+
guild_id?: string;
750+
workspace_id?: string;
751+
chat_id?: string;
752+
channel_ids?: string[];
753+
dm_allowed_users?: string[];
754+
}
755+
756+
export interface UpdateBindingResponse {
757+
success: boolean;
758+
message: string;
759+
}
760+
741761
export interface DeleteBindingRequest {
742762
agent_id: string;
743763
channel: string;
@@ -775,6 +795,15 @@ export interface GlobalSettingsUpdateResponse {
775795
requires_restart: boolean;
776796
}
777797

798+
export interface RawConfigResponse {
799+
content: string;
800+
}
801+
802+
export interface RawConfigUpdateResponse {
803+
success: boolean;
804+
message: string;
805+
}
806+
778807
export const api = {
779808
status: () => fetchJson<StatusResponse>("/status"),
780809
overview: () => fetchJson<InstanceOverviewResponse>("/overview"),
@@ -1023,6 +1052,18 @@ export const api = {
10231052
return response.json() as Promise<CreateBindingResponse>;
10241053
},
10251054

1055+
updateBinding: async (request: UpdateBindingRequest) => {
1056+
const response = await fetch(`${API_BASE}/bindings`, {
1057+
method: "PUT",
1058+
headers: { "Content-Type": "application/json" },
1059+
body: JSON.stringify(request),
1060+
});
1061+
if (!response.ok) {
1062+
throw new Error(`API error: ${response.status}`);
1063+
}
1064+
return response.json() as Promise<UpdateBindingResponse>;
1065+
},
1066+
10261067
deleteBinding: async (request: DeleteBindingRequest) => {
10271068
const response = await fetch(`${API_BASE}/bindings`, {
10281069
method: "DELETE",
@@ -1050,6 +1091,20 @@ export const api = {
10501091
return response.json() as Promise<GlobalSettingsUpdateResponse>;
10511092
},
10521093

1094+
// Raw config API
1095+
rawConfig: () => fetchJson<RawConfigResponse>("/config/raw"),
1096+
updateRawConfig: async (content: string) => {
1097+
const response = await fetch(`${API_BASE}/config/raw`, {
1098+
method: "PUT",
1099+
headers: { "Content-Type": "application/json" },
1100+
body: JSON.stringify({ content }),
1101+
});
1102+
if (!response.ok) {
1103+
throw new Error(`API error: ${response.status}`);
1104+
}
1105+
return response.json() as Promise<RawConfigUpdateResponse>;
1106+
},
1107+
10531108
// Update API
10541109
updateCheck: () => fetchJson<UpdateStatus>("/update/check"),
10551110
updateCheckNow: async () => {
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import {useState, useRef, KeyboardEvent} from "react";
2+
import {X} from "lucide-react";
3+
import {Input} from "@/ui";
4+
5+
interface TagInputProps {
6+
value: string[];
7+
onChange: (tags: string[]) => void;
8+
placeholder?: string;
9+
className?: string;
10+
}
11+
12+
export function TagInput({value, onChange, placeholder, className}: TagInputProps) {
13+
const [inputValue, setInputValue] = useState("");
14+
const inputRef = useRef<HTMLInputElement>(null);
15+
16+
const addTag = (tag: string) => {
17+
const trimmed = tag.trim();
18+
if (trimmed && !value.includes(trimmed)) {
19+
onChange([...value, trimmed]);
20+
setInputValue("");
21+
}
22+
};
23+
24+
const removeTag = (tagToRemove: string) => {
25+
onChange(value.filter((tag) => tag !== tagToRemove));
26+
};
27+
28+
const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
29+
if (e.key === "Enter") {
30+
e.preventDefault();
31+
addTag(inputValue);
32+
} else if (e.key === "Backspace" && !inputValue && value.length > 0) {
33+
removeTag(value[value.length - 1]);
34+
}
35+
};
36+
37+
const handleBlur = () => {
38+
if (inputValue.trim()) {
39+
addTag(inputValue);
40+
}
41+
};
42+
43+
return (
44+
<div className={className}>
45+
<div className="flex flex-wrap gap-2 p-2 border border-paper-darker rounded-md bg-paper-dark min-h-[42px] focus-within:ring-2 focus-within:ring-ink-faint/20">
46+
{value.map((tag) => (
47+
<div
48+
key={tag}
49+
className="flex items-center gap-1 px-2 py-1 bg-paper-darker border border-paper-darkest rounded text-sm text-ink"
50+
>
51+
<span>{tag}</span>
52+
<button
53+
type="button"
54+
onClick={() => removeTag(tag)}
55+
className="text-ink-faint hover:text-ink transition-colors"
56+
>
57+
<X size={14} />
58+
</button>
59+
</div>
60+
))}
61+
<input
62+
ref={inputRef}
63+
type="text"
64+
value={inputValue}
65+
onChange={(e) => setInputValue(e.target.value)}
66+
onKeyDown={handleKeyDown}
67+
onBlur={handleBlur}
68+
placeholder={value.length === 0 ? placeholder : ""}
69+
className="flex-1 min-w-[120px] bg-transparent border-none outline-none text-sm text-ink placeholder:text-ink-faint"
70+
/>
71+
</div>
72+
</div>
73+
);
74+
}

0 commit comments

Comments
 (0)