Browse Source

Impl AiAssistantTree

Shun Miyazawa 1 year ago
parent
commit
a1a58787ba

+ 22 - 11
apps/app/src/features/openai/client/components/AiAssistant/Sidebar/AiAssistantSubstance.tsx

@@ -1,6 +1,8 @@
 import React from 'react';
 import React from 'react';
 
 
-import { useAiAssistantManagementModal } from '../../../stores/ai-assistant';
+import { useAiAssistantManagementModal, useSWRxAiAssistants } from '../../../stores/ai-assistant';
+
+import { AiAssistantTree } from './AiAssistantTree';
 
 
 import styles from './AiAssistantSubstance.module.scss';
 import styles from './AiAssistantSubstance.module.scss';
 
 
@@ -8,28 +10,37 @@ const moduleClass = styles['grw-ai-assistant-substance'] ?? '';
 
 
 export const AiAssistantContent = (): JSX.Element => {
 export const AiAssistantContent = (): JSX.Element => {
   const { open } = useAiAssistantManagementModal();
   const { open } = useAiAssistantManagementModal();
+  const { data: aiAssistants } = useSWRxAiAssistants();
 
 
   return (
   return (
     <div className={moduleClass}>
     <div className={moduleClass}>
       <button
       <button
         type="button"
         type="button"
-        className="btn btn-outline-secondary px-3 d-flex align-items-center mb-3"
+        className="btn btn-outline-secondary px-3 d-flex align-items-center mb-4"
         onClick={open}
         onClick={open}
       >
       >
         <span className="material-symbols-outlined fs-5 me-2">add</span>
         <span className="material-symbols-outlined fs-5 me-2">add</span>
         <span className="fw-normal">アシスタントを追加する</span>
         <span className="fw-normal">アシスタントを追加する</span>
       </button>
       </button>
 
 
-      <div className="py-4 d-flex">
-        <h3 className="fw-bold mb-0 grw-ai-assistant-substance-header">
-          マイアシスタント
-        </h3>
-      </div>
+      <div className="d-flex flex-column gap-4">
+        <div>
+          <h3 className="fw-bold grw-ai-assistant-substance-header">
+            マイアシスタント
+          </h3>
+          {aiAssistants?.myAiAssistants != null && aiAssistants.myAiAssistants.length !== 0 && (
+            <AiAssistantTree aiAssistants={aiAssistants.myAiAssistants} />
+          )}
+        </div>
 
 
-      <div className="py-4 d-flex">
-        <h3 className="fw-bold mb-0 grw-ai-assistant-substance-header">
-          チームアシスタント
-        </h3>
+        <div>
+          <h3 className="fw-bold grw-ai-assistant-substance-header">
+            チームアシスタント
+          </h3>
+          {aiAssistants?.teamAiAssistants != null && aiAssistants.teamAiAssistants.length !== 0 && (
+            <AiAssistantTree aiAssistants={aiAssistants.teamAiAssistants} />
+          )}
+        </div>
       </div>
       </div>
     </div>
     </div>
   );
   );

+ 25 - 0
apps/app/src/features/openai/client/components/AiAssistant/Sidebar/AiAssistantTree.module.scss

@@ -0,0 +1,25 @@
+.grw-ai-assistant-tree-container :global {
+  .grw-ai-assistant-item-container {
+    .list-group-item {
+      .grw-ai-assistant-triangle-btn {
+        border: 0;
+        transition: transform 0.2s ease-out;
+        transform: rotate(0deg);
+
+        &.grw-ai-assistant-open {
+          transform: rotate(90deg);
+        }
+      }
+
+      .grw-triangle-container {
+        height: 30px;
+      }
+
+      .grw-ai-assistant-title-anchor {
+        width: 100%;
+        overflow: hidden;
+        font-size: 14px;
+      }
+    }
+  }
+}

+ 142 - 0
apps/app/src/features/openai/client/components/AiAssistant/Sidebar/AiAssistantTree.tsx

@@ -0,0 +1,142 @@
+import React, { useCallback, useState } from 'react';
+
+import { AiAssistantShareScope, type AiAssistantHasId } from '../../../../interfaces/ai-assistant';
+
+import styles from './AiAssistantTree.module.scss';
+
+
+type ThreadItemProps = {
+  name: string;
+  onClick?: () => void;
+};
+
+const ThreadItem: React.FC<ThreadItemProps> = ({
+  name,
+  onClick,
+}) => {
+  return (
+    <li
+      className="list-group-item list-group-item-action border-0  d-flex align-items-center rounded-1 ps-5"
+      onClick={onClick}
+      role="button"
+    >
+      <div>
+        <span className="material-symbols-outlined fs-5">chat</span>
+      </div>
+      <div className="grw-ai-assistant-title-anchor ps-1">
+        <p className="text-truncate m-auto">{name}</p>
+      </div>
+    </li>
+  );
+};
+
+
+type AiAssistantItemProps = {
+  name: string;
+  type: AiAssistantShareScope;
+  threads: { id: string; name: string }[]; // dummy data
+  onThreadClick?: (threadId: string) => void;
+};
+
+const AiAssistantItem: React.FC<AiAssistantItemProps> = ({
+  name,
+  type,
+  threads,
+  onThreadClick,
+}) => {
+  const [isExpanded, setIsExpanded] = useState(false);
+
+  const handleToggle = useCallback(() => {
+    setIsExpanded(prev => !prev);
+  }, []);
+
+  const handleButtonClick = useCallback((e: React.MouseEvent) => {
+    e.stopPropagation();
+    handleToggle();
+  }, [handleToggle]);
+
+  const getShareScopeIcon = (shareScope: AiAssistantShareScope): string => {
+    switch (shareScope) {
+      case AiAssistantShareScope.OWNER:
+        return 'lock';
+      case AiAssistantShareScope.GROUPS:
+        return 'polyline';
+      case AiAssistantShareScope.PUBLIC_ONLY:
+        return 'group';
+      case AiAssistantShareScope.SAME_AS_ACCESS_SCOPE:
+        return 'group';
+    }
+  };
+
+  return (
+    <div className="grw-ai-assistant-item-container">
+      <li
+        className="list-group-item list-group-item-action border-0 py- d-flex align-items-center rounded-1"
+        onClick={handleToggle}
+        role="button"
+      >
+        <div className="grw-triangle-container d-flex justify-content-center">
+          <button
+            type="button"
+            className={`grw-ai-assistant-triangle-btn btn px-0 ${isExpanded ? 'grw-ai-assistant-open' : ''}`}
+            onClick={handleButtonClick}
+          >
+            <div className="d-flex justify-content-center">
+              <span className="material-symbols-outlined fs-5">arrow_right</span>
+            </div>
+          </button>
+        </div>
+        <div>
+          <span className="material-symbols-outlined fs-5">{getShareScopeIcon(type)}</span>
+        </div>
+        <div className="grw-ai-assistant-title-anchor ps-1">
+          <p className="text-truncate m-auto">{name}</p>
+        </div>
+      </li>
+
+      {isExpanded && threads.length > 0 && (
+        <div className="grw-ai-assistant-item-children">
+          {threads.map(thread => (
+            <ThreadItem
+              key={thread.id}
+              name={thread.name}
+              onClick={() => onThreadClick?.(thread.id)}
+            />
+          ))}
+        </div>
+      )}
+    </div>
+  );
+};
+
+
+// hardcoded data
+const dummyThreads = [
+  { id: '1', name: 'thread1' },
+  { id: '2', name: 'thread2' },
+  { id: '3', name: 'thread3' },
+];
+
+// Tree Component
+type AiAssistantTreeProps = {
+  aiAssistants: AiAssistantHasId[];
+  onThreadClick?: (threadId: string) => void;
+};
+
+export const AiAssistantTree: React.FC<AiAssistantTreeProps> = ({ aiAssistants, onThreadClick }) => {
+  return (
+    <div className={`${styles['grw-ai-assistant-tree-container']} grw-ai-assistant-item-container`}>
+      <ul className="grw-ai-assistant-tree list-group">
+        {aiAssistants.map(assistant => (
+          <AiAssistantItem
+            key={assistant._id}
+            name={assistant.name}
+            type={assistant.shareScope}
+            threads={dummyThreads}
+            onThreadClick={onThreadClick}
+          />
+        ))}
+      </ul>
+    </div>
+  );
+};