Skip to content

Commit 2bbbb92

Browse files
shahrukh802ArslanSaleemgventuri
authored
[PAN-822]: sidebar status persisted (#1229)
* feat(pandasai_server): pandasai server code commit * fix(docker-compose): add pandasbi backend in docker * fix(pandasai-lint): sort linting errors * refactor: rename modle file name * feat(chat): adding chat endpoint * fix: remove some keywords from spellcheck * fix: poetry lock file * me api integrated * chat api integrated * renamed, fix and add documentation * workspace replaced * fix: add conversation id to response * feat(conversation): return all user conversations * fix: should be equal equal * feat(pandasai_server): pandasai server code commit (#1189) * feat(pandasai_server): pandasai server code commit * fix(docker-compose): add pandasbi backend in docker * fix(pandasai-lint): sort linting errors * refactor: rename modle file name * feat(chat): adding chat endpoint * fix: remove some keywords from spellcheck * fix: poetry lock file * me api integrated * chat api integrated * renamed, fix and add documentation * workspace replaced * fix: add conversation id to response * feat(conversation): return all user conversations * fix: should be equal equal --------- Co-authored-by: Shahrukh Khan <srkbannu@gmail.com> * get all datasets endpoint completed * feat(conversation_messages): return conversation messages (#1196) * get all datasets endpoint completed (#1197) * chore: update poetry.lock * chore: add pgdata to gitignore * chore: fix postgres url in the server .env example * chore: add .env.example for the client * fix: add rollbar * fix: wrong redirects * fix: docker compose to wait for postgres before starting (#1198) * fix: response message should be value for strings and numbers (#1199) * docs: mention the platform in the docs * style: hide rightbar for now * style: move the "new chat" button next to chat * [PAN-816]: Past conversations integrated and workspace removed (#1200) * feat(conversation_messages): return conversation messages * past conversations integrated * workspace page removed * workspace page removed * workspace page removed * workspace page removed * bugfix --------- Co-authored-by: ArslanSaleem <khan.arslan38@gmail.com> * style: remove tooltip from the sidebar * style: clean up the UI * chore: fix typo in volume name * conversation actions removed * conversation actions removed (#1201) * fix: filter out empty conversations * docs: add platform in the docs * get users by workspace id api added * docs: add docs about the platform * pydantic response for workspace user added * workspace datasets endpoint added * archive conversation api added (#1209) * [PAN-820]: archive api integrated on frontend (#1210) * archive conversation api added * archive conversation integrated on frontend * chore: add link about the roadmap * past conversation issues fixed * [PAN-824]: Ui stuck fixed (#1215) * ui stuck fixed * docker fixed * style: ctas in the sidebar fade out on collapse * scroll to bottom bug fix (#1217) * tests(pandasai): write python test cases (#1216) * new conversation not adding to sidebar fixed * fix: frontend docker build (#1219) * merge main to release * dataframe table design improved * dataframe table design improved (#1227) * sidebar status persisted * scroll added to table --------- Co-authored-by: ArslanSaleem <khan.arslan38@gmail.com> Co-authored-by: Gabriele Venturi <lele.venturi@gmail.com>
1 parent 668e6fe commit 2bbbb92

16 files changed

Lines changed: 208 additions & 213 deletions

File tree

client/.eslintrc.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"react/no-unescaped-entities": ["error", { "forbid": [">", "}"] }],
2323
"@typescript-eslint/no-explicit-any": "off",
2424
"no-useless-catch": "off",
25-
"react-hooks/exhaustive-deps": 0
25+
"react-hooks/exhaustive-deps": 0,
26+
"react/prop-types": "off"
2627
}
2728
}

client/Providers/AppProvider.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import "styles/globals.css";
1818
import "styles/App.css";
1919
import "styles/multi-range-slider.css";
2020
import "react-toastify/dist/ReactToastify.css";
21-
import "react-tooltip/dist/react-tooltip.css";
2221

2322
const _NoSSR = ({ children }) => <React.Fragment>{children}</React.Fragment>;
2423
const NoSSR = dynamic(() => Promise.resolve(_NoSSR), {

client/components/AddUserModal/index.tsx

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import { useAddSpaceUsers, useDeleteSpaceUsers } from "hooks/useSpaces";
55
import { Loader } from "components/loader/Loader";
66
import { toast } from "react-toastify";
77
import { useGetMembersList, useGetOrganizations } from "hooks/useOrganizations";
8-
import { Tooltip } from "react-tooltip";
98
import { Input } from "../ui/input";
9+
import AppTooltip from "../AppTooltip";
1010

1111
interface ISpaceUser {
1212
space_id: string;
@@ -111,15 +111,13 @@ const AddUserModal = ({ setIsModelOpen, spaceUsers, spaceId }: IProps) => {
111111
className="cursor-pointer min-w-[50px] border-white/0 py-3 pr-4 pl-4 flex"
112112
onClick={() => handleDeleteSpaceUser(user?.user_id)}
113113
>
114-
<AiFillDelete id="deleteIcon" color="#ccc" size="1.5em" />
115-
116-
<Tooltip
117-
anchorSelect="#deleteIcon"
118-
className="z-10"
119-
opacity={1}
120-
>
121-
You cannot remove the sole user from this workspace
122-
</Tooltip>
114+
<AppTooltip text="You cannot remove the sole user from this workspace">
115+
<AiFillDelete
116+
id="deleteIcon"
117+
color="#ccc"
118+
size="1.5em"
119+
/>
120+
</AppTooltip>
123121
</span>
124122
) : (
125123
<span
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
"use client";
2+
import React from "react";
3+
import {
4+
Tooltip,
5+
TooltipContent,
6+
TooltipProvider,
7+
TooltipTrigger,
8+
} from "@/components/ui/tooltip";
9+
10+
interface IProps {
11+
text: string;
12+
children: React.ReactNode;
13+
}
14+
15+
const AppTooltip = ({ text, children }: IProps) => {
16+
return (
17+
<TooltipProvider>
18+
<Tooltip>
19+
<TooltipTrigger>{children}</TooltipTrigger>
20+
<TooltipContent>
21+
<p>{text}</p>
22+
</TooltipContent>
23+
</Tooltip>
24+
</TooltipProvider>
25+
);
26+
};
27+
28+
export default AppTooltip;

client/components/ChatScreen/AIChatBubble.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ const AIChatBubble = ({ chat, lastIndex }: IProps) => {
3535
/>
3636
</div>
3737

38-
<div className="flex flex-1 flex-col">
38+
<div className="flex w-full flex-col">
3939
<span className="dark:text-white font-bold text-lg">PandaBI</span>
4040
<div
4141
className={`break-all text-sm md:text-[15px] font-medium w-auto overflow-visible dark:text-white
@@ -58,7 +58,6 @@ const AIChatBubble = ({ chat, lastIndex }: IProps) => {
5858
<ChatDataFrame
5959
chatResponse={response}
6060
chatId={chat.id}
61-
index={indx}
6261
key={chat?.id}
6362
/>
6463
</>

client/components/ChatScreen/ChatDataFrame.tsx

Lines changed: 62 additions & 180 deletions
Original file line numberDiff line numberDiff line change
@@ -1,202 +1,84 @@
11
"use client";
2-
import { FetchDataframe } from "services/chat";
32
import { ChatResponseItem } from "@/types/chat-types";
43
import React, { useState, useRef, useEffect } from "react";
5-
import { FaChevronDown, FaChevronUp } from "react-icons/fa";
6-
import { toast } from "react-toastify";
4+
import { Grid } from "gridjs";
5+
import DownloadIcon from "../Icons/DownloadIcon";
6+
import SearchIcon from "../Icons/SearchIcon";
7+
import { useAppStore } from "@/store";
8+
import AppTooltip from "../AppTooltip";
9+
import { convertToCSV } from "@/utils/convertToCSV";
10+
import "gridjs/dist/theme/mermaid.css";
711

812
interface IProps {
913
chatResponse: ChatResponseItem;
10-
index: number;
1114
chatId: string;
1215
}
1316

14-
const StyledRecord = ({ record }: { record: string }) => {
15-
if (!record) return record;
16-
17-
if (record === "true" || record === "True") {
18-
return (
19-
<span className="text-green-500">
20-
<svg
21-
xmlns="http://www.w3.org/2000/svg"
22-
className="h-4 w-4 inline"
23-
fill="none"
24-
viewBox="0 0 24 24"
25-
stroke="currentColor"
26-
>
27-
<path
28-
strokeLinecap="round"
29-
strokeLinejoin="round"
30-
strokeWidth={2}
31-
d="M5 13l4 4L19 7"
32-
/>
33-
</svg>
34-
</span>
35-
);
36-
} else if (record === "false" || record === "False") {
37-
return (
38-
<span className="text-red-500">
39-
<svg
40-
xmlns="http://www.w3.org/2000/svg"
41-
className="h-4 w-4 inline"
42-
fill="none"
43-
viewBox="0 0 24 24"
44-
stroke="currentColor"
45-
>
46-
<path
47-
strokeLinecap="round"
48-
strokeLinejoin="round"
49-
strokeWidth={2}
50-
d="M6 18L18 6M6 6l12 12"
51-
/>
52-
</svg>
53-
</span>
54-
);
55-
} else if (record.includes("http") || record.includes("www")) {
56-
return (
57-
<a
58-
href={record}
59-
target="_blank"
60-
rel="noreferrer noopener"
61-
className="text-[#86ade4] hover:underline"
62-
>
63-
{record}
64-
</a>
65-
);
66-
} else if (record.includes("@") && record.includes(".")) {
67-
return (
68-
<a href={`mailto:${record}`} className="text-[#86ade4] hover:underline">
69-
{record}
70-
</a>
71-
);
72-
} else {
73-
const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
74-
if (dateRegex.test(record)) {
75-
const date = new Date(record);
76-
const options: Intl.DateTimeFormatOptions = {
77-
year: "numeric",
78-
month: "long",
79-
day: "numeric",
80-
hour: "numeric",
81-
minute: "numeric",
82-
};
83-
return date.toLocaleDateString("en-US", options);
84-
} else {
85-
return record;
86-
}
87-
}
88-
};
89-
90-
const ChatDataFrame = ({ chatResponse, index, chatId }: IProps) => {
91-
const [expandedCardId, setExpandedCardId] = useState(null);
92-
const [contentHeight, setContentHeight] = useState("300px");
93-
const [isDownloading, setIsDownloading] = useState(false);
17+
const ChatDataFrame = ({ chatResponse, chatId }: IProps) => {
9418
const contentRef = useRef(null);
19+
const [search, setSearch] = useState(false);
20+
const darkMode = useAppStore((state) => state.darkMode);
21+
22+
const grid = new Grid({
23+
columns: chatResponse?.value?.headers,
24+
data: chatResponse?.value?.rows,
25+
sort: true,
26+
search: true,
27+
pagination: {
28+
limit: 10,
29+
summary: false,
30+
},
31+
style: {
32+
table: {
33+
"white-space": "nowrap",
34+
},
35+
},
36+
height: "600px",
37+
resizable: true,
38+
});
9539

9640
useEffect(() => {
97-
if (expandedCardId === index && contentRef.current) {
98-
setContentHeight(`${contentRef.current.scrollHeight}px`);
99-
} else {
100-
setContentHeight("300px");
101-
}
102-
}, [expandedCardId, index]);
41+
grid.render(contentRef.current);
42+
});
43+
44+
const handleSearch = () => {
45+
const gridJsHead = contentRef.current.querySelector(".gridjs-head");
10346

104-
const handleToggleExpand = (index) => {
105-
setExpandedCardId((prevId) => (prevId === index ? null : index));
47+
if (gridJsHead) {
48+
gridJsHead.style.display = search ? "none" : "block";
49+
setSearch(!search);
50+
}
10651
};
10752

108-
const handleDownload = async (id: string) => {
109-
setIsDownloading(true);
110-
await FetchDataframe(id)
111-
.then((response) => {
112-
const fileUrl = response?.data?.data?.download_url;
113-
const link = document.createElement("a");
114-
link.href = fileUrl;
115-
link.setAttribute("download", `dataframe-${id}`);
116-
document.body.appendChild(link);
117-
link.click();
118-
document.body.removeChild(link);
119-
})
120-
.catch((error) => {
121-
toast.error(
122-
error?.response?.data?.message
123-
? error.response.data.message
124-
: error.message
125-
);
126-
})
127-
.finally(() => setIsDownloading(false));
53+
const handleDownload = async () => {
54+
const { headers, rows } = chatResponse.value;
55+
const csvData = convertToCSV(headers, rows);
56+
const blob = new Blob([csvData], { type: "text/csv" });
57+
const url = window.URL.createObjectURL(blob);
58+
const a = document.createElement("a");
59+
a.setAttribute("href", url);
60+
a.setAttribute("download", `dataframe-${chatId}.csv`);
61+
document.body.appendChild(a);
62+
a.click();
63+
document.body.removeChild(a);
64+
window.URL.revokeObjectURL(url);
12865
};
12966

13067
return (
131-
<div className="mt-2">
132-
<div
133-
ref={contentRef}
134-
className={`relative bg-white dark:bg-[#333333] rounded-[20px] px-4 py-2 custom-scroll overflow-hidden overflow-x-auto transition-all`}
135-
style={{ maxHeight: contentHeight, height: "auto" }}
136-
>
137-
<table className="overflow-auto w-full">
138-
<thead>
139-
<tr className="!border-px !border-gray-400 uppercase p-2">
140-
{chatResponse?.value?.headers?.map((item, index) => (
141-
<th
142-
key={index}
143-
className="cursor-pointer border-b p-2 text-center border-solid border-r last:border-r-0 text-xs min-w-[100px]"
144-
>
145-
{item.split("_").join(" ")}
146-
</th>
147-
))}
148-
</tr>
149-
</thead>
150-
<tbody>
151-
{chatResponse?.value?.rows?.map((rows, index) => (
152-
<tr className="cursor-pointer" key={index}>
153-
{Object.values(rows).map((item, index) => (
154-
<td
155-
key={index}
156-
className="text-center border-solid border-r dark:border-white border-[rgba(0,0,0,0.10)] last:border-r-0 pt-3"
157-
>
158-
<StyledRecord record={`${item}`} />
159-
</td>
160-
))}
161-
</tr>
162-
))}
163-
</tbody>
164-
</table>
165-
{chatResponse?.value?.rows?.length > 3 && (
166-
<div
167-
className={`absolute bottom-0 left-0 right-0 h-20 bg-gradient-to-t from-white dark:from-[#191919] to-transparent pointer-events-none ${
168-
expandedCardId !== index ? "opacity-100" : "opacity-0"
169-
} transition-all`}
170-
></div>
171-
)}
172-
173-
{expandedCardId === index && (
174-
<div className="text-center mt-6">
175-
<button
176-
className="cursor-pointer bg-[#191919] h-[24px] text-xs text-white font-bold rounded-full px-3 py-1"
177-
onClick={() => {
178-
handleDownload(chatId);
179-
}}
180-
>
181-
{isDownloading ? "Downloading..." : "Download the full table"}
182-
</button>
183-
</div>
184-
)}
185-
</div>
186-
<div className="text-center mt-2 flex flex-col gap-2 items-center justify-center">
187-
{chatResponse?.value?.rows?.length > 3 && (
188-
<button
189-
className="h-6 w-6 flex items-center justify-center text-white bg-[#191919] rounded-full"
190-
onClick={() => handleToggleExpand(index)}
191-
>
192-
{expandedCardId === index ? (
193-
<FaChevronUp size="1em" />
194-
) : (
195-
<FaChevronDown size="1em" />
196-
)}
197-
</button>
198-
)}
68+
<div className="flex flex-col mt-2">
69+
<div className="flex justify-end">
70+
<div className="cursor-pointer" onClick={handleSearch}>
71+
<AppTooltip text="Search">
72+
<SearchIcon color={darkMode ? "#fff" : "#000"} />
73+
</AppTooltip>
74+
</div>
75+
<div className="cursor-pointer" onClick={handleDownload}>
76+
<AppTooltip text="Download">
77+
<DownloadIcon color={darkMode ? "#fff" : "#000"} />
78+
</AppTooltip>
79+
</div>
19980
</div>
81+
<div ref={contentRef} className={`grid-container${chatId}`} />
20082
</div>
20183
);
20284
};
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import React from "react";
2+
3+
const DownloadIcon = ({ color = "white" }: { color?: string }) => {
4+
return (
5+
<svg
6+
width="24"
7+
height="24"
8+
viewBox="0 0 24 24"
9+
fill="none"
10+
xmlns="http://www.w3.org/2000/svg"
11+
>
12+
<path
13+
d="M12 15.577L8.461 12.039L9.169 11.319L11.5 13.65V5H12.5V13.65L14.83 11.32L15.539 12.039L12 15.577ZM6.616 19C6.15533 19 5.771 18.846 5.463 18.538C5.155 18.23 5.00067 17.8453 5 17.384V14.961H6V17.384C6 17.538 6.064 17.6793 6.192 17.808C6.32 17.9367 6.461 18.0007 6.615 18H17.385C17.5383 18 17.6793 17.936 17.808 17.808C17.9367 17.68 18.0007 17.5387 18 17.384V14.961H19V17.384C19 17.8447 18.846 18.229 18.538 18.537C18.23 18.845 17.8453 18.9993 17.384 19H6.616Z"
14+
fill={color}
15+
/>
16+
</svg>
17+
);
18+
};
19+
20+
export default DownloadIcon;

0 commit comments

Comments
 (0)