Skip to content

Commit ee4a06b

Browse files
authored
feat: web components assistant (#422)
* chore: web components assistant * chore: web components assistant * docs: update notes
1 parent 9715a92 commit ee4a06b

18 files changed

Lines changed: 132 additions & 137 deletions

File tree

.env

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
COCO_SERVER_URL=https://coco.infini.cloud #http://localhost:9000
1+
COCO_SERVER_URL=http://localhost:9000 #https://coco.infini.cloud #http://localhost:9000
22

3-
COCO_WEBSOCKET_URL=wss://coco.infini.cloud/ws #ws://localhost:9000/ws
3+
COCO_WEBSOCKET_URL=ws://localhost:9000/ws #wss://coco.infini.cloud/ws #ws://localhost:9000/ws

docs/content.en/docs/release-notes/_index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ Information about release notes of Coco Server is provided here.
2727
- feat: add support for AI assistant #394
2828
- feat: add support for calculator function #399
2929
- feat: auto selects the first item after searching #411
30+
- feat: web components assistant #422
3031

3132
### Bug fix
3233

public/assets/fonts/icons/iconfont.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/components/Assistant/AssistantList.tsx

Lines changed: 59 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,13 @@ import FontIcon from "@/components/Common/Icons/FontIcon";
1212
import { useChatStore } from "@/stores/chatStore";
1313
import { AI_ASSISTANT_PANEL_ID } from "@/constants";
1414
import { useShortcutsStore } from "@/stores/shortcutsStore";
15+
import { Get } from "@/api/axiosRequest";
1516

1617
interface AssistantListProps {
17-
showChatHistory?: boolean;
18+
assistantIDs?: string[];
1819
}
1920

20-
export function AssistantList({ showChatHistory = true }: AssistantListProps) {
21+
export function AssistantList({ assistantIDs = [] }: AssistantListProps) {
2122
const { t } = useTranslation();
2223
const { connected } = useChatStore();
2324
const isTauri = useAppStore((state) => state.isTauri);
@@ -35,34 +36,49 @@ export function AssistantList({ showChatHistory = true }: AssistantListProps) {
3536
useClickAway(menuRef, () => setIsOpen(false));
3637
const [assistants, setAssistants] = useState<any[]>([]);
3738

38-
const fetchAssistant = useCallback(async (serverId: string) => {
39-
if (!isTauri) return;
40-
if (!serverId) return;
41-
platformAdapter
42-
.commands("assistant_search", {
43-
serverId,
44-
})
45-
.then((res: any) => {
46-
res = res ? JSON.parse(res) : null;
47-
console.log("assistant_search", res);
48-
const assistantList = res?.hits?.hits || [];
49-
setAssistants(assistantList);
50-
if (assistantList.length > 0) {
51-
const assistant = assistantList.find(
52-
(item: any) => item._id === currentAssistant?._id
53-
);
54-
if (assistant) {
55-
setCurrentAssistant(assistant);
56-
} else {
57-
setCurrentAssistant(assistantList[0]);
58-
}
59-
}
60-
})
61-
.catch((err: any) => {
39+
const fetchAssistant = useCallback(async (serverId?: string) => {
40+
let response: any;
41+
if (isTauri) {
42+
if (!serverId) return;
43+
try {
44+
response = await platformAdapter.commands("assistant_search", {
45+
serverId,
46+
});
47+
response = response ? JSON.parse(response) : null;
48+
} catch (err) {
6249
setAssistants([]);
6350
setCurrentAssistant(null);
64-
console.log("assistant_search", err);
65-
});
51+
console.error("assistant_search", err);
52+
}
53+
} else {
54+
const [error, res] = await Get(`/assistant/_search`);
55+
if (error) {
56+
setAssistants([]);
57+
setCurrentAssistant(null);
58+
console.error("assistant_search", error);
59+
return;
60+
}
61+
console.log("/assistant/_search", res);
62+
response = res;
63+
}
64+
console.log("assistant_search", response);
65+
let assistantList = response?.hits?.hits || [];
66+
67+
assistantList = assistantIDs.length > 0
68+
? assistantList.filter((item: any) => assistantIDs.includes(item._id))
69+
: assistantList;
70+
71+
setAssistants(assistantList);
72+
if (assistantList.length > 0) {
73+
const assistant = assistantList.find(
74+
(item: any) => item._id === currentAssistant?._id
75+
);
76+
if (assistant) {
77+
setCurrentAssistant(assistant);
78+
} else {
79+
setCurrentAssistant(assistantList[0]);
80+
}
81+
}
6682
}, []);
6783

6884
useEffect(() => {
@@ -98,24 +114,22 @@ export function AssistantList({ showChatHistory = true }: AssistantListProps) {
98114
<div className="max-w-[100px] truncate">
99115
{currentAssistant?._source?.name || "Coco AI"}
100116
</div>
101-
{showChatHistory && isTauri && (
102-
<VisibleKey
103-
aria-controls={isOpen ? AI_ASSISTANT_PANEL_ID : ""}
104-
shortcut={aiAssistant}
105-
onKeyPress={() => {
106-
setIsOpen(!isOpen);
107-
}}
108-
>
109-
<ChevronDownIcon
110-
className={`size-4 text-gray-500 dark:text-gray-400 transition-transform ${
111-
isOpen ? "rotate-180" : ""
112-
}`}
113-
/>
114-
</VisibleKey>
115-
)}
117+
<VisibleKey
118+
aria-controls={isOpen ? AI_ASSISTANT_PANEL_ID : ""}
119+
shortcut={aiAssistant}
120+
onKeyPress={() => {
121+
setIsOpen(!isOpen);
122+
}}
123+
>
124+
<ChevronDownIcon
125+
className={`size-4 text-gray-500 dark:text-gray-400 transition-transform ${
126+
isOpen ? "rotate-180" : ""
127+
}`}
128+
/>
129+
</VisibleKey>
116130
</button>
117131

118-
{showChatHistory && isTauri && isOpen && (
132+
{isOpen && (
119133
<div
120134
id={isOpen ? AI_ASSISTANT_PANEL_ID : ""}
121135
className="absolute z-50 top-full mt-1 left-0 w-64 rounded-xl bg-white dark:bg-[#202126] p-2 text-sm/6 text-gray-800 dark:text-white shadow-lg border border-gray-200 dark:border-gray-700 focus:outline-none max-h-[calc(100vh-80px)] overflow-y-auto"
@@ -140,6 +154,7 @@ export function AssistantList({ showChatHistory = true }: AssistantListProps) {
140154
<button
141155
key={assistant._id}
142156
onClick={() => {
157+
console.log("assistant", assistant);
143158
setCurrentAssistant(assistant);
144159
setIsOpen(false);
145160
}}

src/components/Assistant/Chat.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ interface ChatAIProps {
3535
isChatPage?: boolean;
3636
getFileUrl: (path: string) => string;
3737
showChatHistory?: boolean;
38+
assistantIDs?: string[];
3839
}
3940

4041
export interface ChatAIRef {
@@ -58,6 +59,7 @@ const ChatAI = memo(
5859
isChatPage = false,
5960
getFileUrl,
6061
showChatHistory,
62+
assistantIDs,
6163
},
6264
ref
6365
) => {
@@ -376,6 +378,7 @@ const ChatAI = memo(
376378
isLogin={isLogin}
377379
setIsLogin={setIsLogin}
378380
showChatHistory={showChatHistory}
381+
assistantIDs={assistantIDs}
379382
/>
380383
{isLogin ? (
381384
<ChatContent

src/components/Assistant/ChatHeader.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
import {
2-
MessageSquarePlus,
3-
} from "lucide-react";
1+
import { MessageSquarePlus } from "lucide-react";
42
import clsx from "clsx";
53

64
import HistoryIcon from "@/icons/History";
@@ -27,6 +25,7 @@ interface ChatHeaderProps {
2725
setIsLogin: (isLogin: boolean) => void;
2826
isChatPage?: boolean;
2927
showChatHistory?: boolean;
28+
assistantIDs?: string[];
3029
}
3130

3231
export function ChatHeader({
@@ -40,8 +39,8 @@ export function ChatHeader({
4039
setIsLogin,
4140
isChatPage = false,
4241
showChatHistory = true,
42+
assistantIDs,
4343
}: ChatHeaderProps) {
44-
4544
const isPinned = useAppStore((state) => state.isPinned);
4645
const setIsPinned = useAppStore((state) => state.setIsPinned);
4746

@@ -55,7 +54,7 @@ export function ChatHeader({
5554
const fixedWindow = useShortcutsStore((state) => {
5655
return state.fixedWindow;
5756
});
58-
57+
5958
const external = useShortcutsStore((state) => state.external);
6059

6160
const togglePin = async () => {
@@ -94,7 +93,7 @@ export function ChatHeader({
9493
</button>
9594
)}
9695

97-
<AssistantList showChatHistory={showChatHistory} />
96+
<AssistantList assistantIDs={assistantIDs} />
9897

9998
{showChatHistory ? (
10099
<button

src/components/Assistant/Greetings.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { useTranslation } from "react-i18next";
22

33
import { ChatMessage } from "@/components/ChatMessage";
4+
import { useConnectStore } from "@/stores/connectStore";
45

56
export const Greetings = () => {
67
const { t } = useTranslation();
8+
const currentAssistant = useConnectStore((state) => state.currentAssistant);
79

810
return (
911
<ChatMessage
@@ -12,7 +14,9 @@ export const Greetings = () => {
1214
_id: "greetings",
1315
_source: {
1416
type: "assistant",
15-
message: t("assistant.chat.greetings"),
17+
message:
18+
currentAssistant?._source?.chat_settings?.greeting_message ||
19+
t("assistant.chat.greetings"),
1620
},
1721
}}
1822
/>

src/components/ChatMessage/PrevSuggestion.tsx

Lines changed: 8 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import { MoveRight } from "lucide-react";
22
import { FC, useEffect, useState } from "react";
33

4-
import { Get } from "@/api/axiosRequest";
5-
import { useAppStore } from "@/stores/appStore";
4+
import { useConnectStore } from "@/stores/connectStore";
65

76
interface PrevSuggestionProps {
87
sendMessage: (message: string) => void;
@@ -11,35 +10,18 @@ interface PrevSuggestionProps {
1110
const PrevSuggestion: FC<PrevSuggestionProps> = (props) => {
1211
const { sendMessage } = props;
1312

14-
const isTauri = useAppStore((state) => state.isTauri);
15-
16-
const headersStr = localStorage.getItem("headers") || "{}";
17-
const headers = JSON.parse(headersStr);
18-
const id = headers["APP-INTEGRATION-ID"] || "cvkm9hmhpcemufsg3vug";
19-
// console.log("id", id);
13+
const currentAssistant = useConnectStore((state) => state.currentAssistant);
2014

2115
const [list, setList] = useState<string[]>([]);
2216

2317
useEffect(() => {
24-
if (!isTauri) getList();
25-
}, [id]);
26-
27-
const getList = async () => {
28-
if (!id) return;
29-
30-
const url = `/integration/${id}/chat/_suggest`;
31-
32-
const [error, res] = await Get(`/integration/${id}/chat/_suggest`);
33-
34-
if (error) {
35-
console.error(url, error);
36-
return setList([]);
18+
const suggested = currentAssistant?._source?.chat_settings?.suggested || {};
19+
if (suggested.enabled) {
20+
setList(suggested.questions || []);
21+
} else {
22+
setList([]);
3723
}
38-
39-
console.log("chat/_suggest", res);
40-
41-
setList(Array.isArray(res) ? res : []);
42-
};
24+
}, [JSON.stringify(currentAssistant)]);
4325

4426
return (
4527
<ul className="absolute left-2 bottom-2 flex flex-col gap-2">

src/components/Search/InputBox.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import SearchPopover from "./SearchPopover";
1515
// import AudioRecording from "../AudioRecording";
1616
import { DataSource } from "@/types/commands";
1717
// import InputExtra from "./InputExtra";
18-
// import { useConnectStore } from "@/stores/connectStore";
18+
import { useConnectStore } from "@/stores/connectStore";
1919
import { useShortcutsStore } from "@/stores/shortcutsStore";
2020
import Copyright from "@/components/Common/Copyright";
2121
import VisibleKey from "@/components/Common/VisibleKey";
@@ -55,7 +55,6 @@ interface ChatInputProps {
5555
getFileMetadata: (path: string) => Promise<any>;
5656
getFileIcon: (path: string, size: number) => Promise<string>;
5757
hideCoco?: () => void;
58-
hasFeature?: string[];
5958
hasModules?: string[];
6059
searchPlaceholder?: string;
6160
chatPlaceholder?: string;
@@ -77,14 +76,16 @@ export default function ChatInput({
7776
isChatPage = false,
7877
getDataSourcesByServer,
7978
setupWindowFocusListener,
80-
hasFeature = ["think", "search", "think_active", "search_active"],
8179
hideCoco,
8280
hasModules = [],
8381
searchPlaceholder,
8482
chatPlaceholder,
8583
}: ChatInputProps) {
8684
const { t } = useTranslation();
8785

86+
const currentAssistant = useConnectStore((state) => state.currentAssistant);
87+
console.log("currentAssistant", currentAssistant);
88+
8889
const showTooltip = useAppStore((state) => state.showTooltip);
8990
const isPinned = useAppStore((state) => state.isPinned);
9091

@@ -437,7 +438,7 @@ export default function ChatInput({
437438
/>
438439
)} */}
439440

440-
{hasFeature.includes("think") && (
441+
{currentAssistant?._source?.config?.visible && (
441442
<button
442443
className={clsx(
443444
"flex items-center gap-1 py-[3px] pl-1 pr-1.5 rounded-md transition hover:bg-[#EDEDED] dark:hover:bg-[#202126]",
@@ -468,15 +469,15 @@ export default function ChatInput({
468469
</button>
469470
)}
470471

471-
{hasFeature.includes("search") && (
472+
{currentAssistant?._source?.datasource?.visible && (
472473
<SearchPopover
473474
isSearchActive={isSearchActive}
474475
setIsSearchActive={setIsSearchActive}
475476
getDataSourcesByServer={getDataSourcesByServer}
476477
/>
477478
)}
478479

479-
{!hasFeature.includes("search") && !hasFeature.includes("think") ? (
480+
{!currentAssistant?._source?.datasource?.visible && !currentAssistant?._source?.config?.visible ? (
480481
<div className="px-[9px]">
481482
<Copyright />
482483
</div>

src/components/Search/SearchListItem.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,16 @@ const SearchListItem: React.FC<SearchListItemProps> = React.memo(
4949
>
5050
<div
5151
className={`${
52-
showListRight ? "max-w-[450px] mobile:w-full" : "flex-1"
52+
showListRight ? "max-w-[450px] mobile:max-w-full mobile:w-full" : "flex-1"
5353
} min-w-0 flex gap-2 items-center justify-start `}
5454
>
5555
<ItemIcon item={item} />
5656
<span className={`text-sm truncate text-left`}>{item?.title}</span>
5757
</div>
5858
{!isTauri && isMobile ? (
59-
<div className="w-full text-xs truncate">{item?.summary}</div>
59+
<div className="w-full text-xs text-gray-500 dark:text-gray-400 truncate">
60+
{item?.summary}
61+
</div>
6062
) : null}
6163
{showListRight && (isTauri || !isMobile) ? (
6264
<ListRight

0 commit comments

Comments
 (0)