Procházet zdrojové kódy

fix: 修改列表展示,ai功能

liaojiaxing před 2 týdny
rodič
revize
4d3c788560

+ 1 - 1
.umirc.ts

@@ -6,7 +6,7 @@ export default defineConfig({
   outputPath: "marketplace",
   esbuildMinifyIIFE: true,
   favicons: [],
-  styles: ["//at.alicdn.com/t/c/font_4840729_qt3oesd105.css"],
+  styles: ["//at.alicdn.com/t/c/font_4840729_5okrvkvahe7.css"],
   scripts: ["//at.alicdn.com/t/c/font_4840729_qpwqs1eruu.js"],
   model: {},
   title: "易码工坊",

+ 13 - 0
src/api/ai.ts

@@ -60,4 +60,17 @@ export const DeleteSession = (data: {
   request("/api/ai/chat-session/delete", {
     method: "post",
     data,
+  });
+
+    /**
+ * 查询应用是否支持文件
+ * @param app_name 应用名称
+ * @param session_id 会话id
+ */
+export const AppParamenters = (params: {
+  app_name: string;
+}) =>
+  request("/api/ai/app-parameters", {
+    method: "get",
+    params,
   });

+ 9 - 5
src/components/ItemCard.tsx

@@ -4,15 +4,18 @@ import { TagOutlined } from "@ant-design/icons";
 export default function AppItem({
   data,
   onClick,
+  extra,
 }: {
   data: any;
   onClick: (id: string) => void;
+  extra?: React.ReactNode
 }) {
   return (
     <div  
       className="relative overflow-hidden pb-2 group col-span-1 bg-white border-2 border-solid border-transparent rounded-lg shadow-sm flex flex-col transition-all duration-200 ease-in-out cursor-pointer hover:shadow-lg"
       onClick={() => onClick(data.id)}
     >
+      {extra}
       <div className="flex pt-[14px] px-[14px] pb-3 h-[66px] items-center gap-3 grow-0 shrink-0">
         <div className="relative shrink-0">
           <span
@@ -24,13 +27,13 @@ export default function AppItem({
         </div>
         <div className="grow w-0 py-[1px]">
           <div className="flex items-center text-sm leading-5 font-semibold text-text-secondary">
-            <div className="truncate" title="CRM客户关系管理系统">
+            <div className="truncate" title={data?.name}>
               {data?.name}
             </div>
           </div>
-          <div className="flex items-center text-[10px] leading-[18px] text-text-tertiary font-medium">
+          {/* <div className="flex items-center text-[10px] leading-[18px] text-text-tertiary font-medium">
             <div className="truncate">作者 {data?.createByName || '易码工坊'}</div>
-          </div>
+          </div> */}
         </div>
       </div>
       <div className="description-wrapper h-[90px] px-[14px] text-xs leading-normal text-text-tertiary ">
@@ -41,7 +44,7 @@ export default function AppItem({
       <div>
         <div className="flex items-center h-5 px-[14px] text-12px">
           <div className="flex items-center space-x-1 text-text-tertiary">
-            <svg
+            {/* <svg
               viewBox="0 0 24 24"
               xmlns="http://www.w3.org/2000/svg"
               width="24"
@@ -50,7 +53,8 @@ export default function AppItem({
               className="remixicon shrink-0 w-3 h-3"
             >
               <path d="M9 2V4H5L4.999 14H18.999L19 4H15V2H20C20.5523 2 21 2.44772 21 3V21C21 21.5523 20.5523 22 20 22H4C3.44772 22 3 21.5523 3 21V3C3 2.44772 3.44772 2 4 2H9ZM18.999 16H4.999L5 20H19L18.999 16ZM17 17V19H15V17H17ZM13 2V7H16L12 11L8 7H11V2H13Z"></path>
-            </svg>
+            </svg> */}
+            <i className="iconfont icon-liulanliang"/>
             <div className="system-xs-regular">{data?.viewCount || 0}</div>
           </div>
           <div className="mx-2 text-text-quaternary system-xs-regular">·</div>

+ 1 - 1
src/components/ai/MarkdownViewer.tsx

@@ -35,7 +35,7 @@ const MarkdownViewer: React.FC<MarkdownViewerProps> = ({ content }) => {
   const [copiedIndex, setCopiedIndex] = useState<number | null>(null);
   const filterContent = useMemo(() => {
     return content
-      .replaceAll("<think>", '<div class="think">')
+      .replaceAll("<think>", '<div class="think"><div class="think-title"><i class="iconfont icon-sikao" style="color: #df9e20;"></i> <span>思考过程</span></div>')
       .replaceAll("</think>", "</div>");
   }, [content]);
 

+ 11 - 0
src/components/ai/index.less

@@ -40,7 +40,18 @@
     border-left: solid 4px #ddd;
   }
 
+  .think-title {
+    font-size: 14px;
+    color: #333;
+  }
+
   img, video {
     max-width: 100%;
   }
+
+  pre {
+    padding: 0;
+    background: none;
+    border: none;
+  }
 }

+ 4 - 1
src/hooks/useChat.ts

@@ -15,6 +15,7 @@ type MessageItem = {
   status: "loading" | "done" | "error" | "stop";
   loading?: boolean;
   footer?: ReactNode;
+  message_files?: string[];
 };
 
 // 后端返回格式
@@ -177,6 +178,7 @@ export function useChat({ app_name, onSuccess, onUpdate, onError }: ChatProps) {
             content: item.query,
             role: "user",
             status: "done",
+            message_files: item.message_files
           },
           {
             id: item.id + "_query",
@@ -258,7 +260,7 @@ export function useChat({ app_name, onSuccess, onUpdate, onError }: ChatProps) {
    * 发起请求
    * @param chat_query 对话内容
    */
-  const onRequest = (chat_query: string) => {
+  const onRequest = (chat_query: string, fields?: string[]) => {
     activeConversation === '1' && setConversationList((list) => {
       return list?.map((item) => {
         return {
@@ -274,6 +276,7 @@ export function useChat({ app_name, onSuccess, onUpdate, onError }: ChatProps) {
         chat_name: activeConversation === "1" ? chat_query : undefined,
         conversation_id:
           activeConversation === "1" ? undefined : activeConversation,
+        fields
       },
       {
         onSuccess: (data) => {

+ 95 - 15
src/pages/ai/Assistant.tsx

@@ -42,9 +42,10 @@ import type { GetProp, GetRef } from "antd";
 import type { ConversationsProps } from "@ant-design/x";
 import type { AgentItem } from "./data";
 import MarkdownViewer from "@/components/ai/MarkdownViewer";
-import { ChangeSessionName, DeleteSession } from "@/api/ai";
+import { ChangeSessionName, DeleteSession, AppParamenters } from "@/api/ai";
 import InfiniteScroll from "react-infinite-scroll-component";
 import "./assistant.less";
+import { UploadFile } from "@/api";
 
 type AssistantProps = {
   agent?: AgentItem;
@@ -87,6 +88,9 @@ const roles: GetProp<typeof Bubble.List, "roles"> = {
 
 export default (props: AssistantProps) => {
   const [senderVal, setSenderVal] = useState("");
+  const [supportFile, setSupportFile] = useState(false);
+  const [fileIds, setFileIds] = useState<Record<string, string>[]>([]);
+  // 聊天相关状态
   const {
     messages,
     setMessages,
@@ -101,7 +105,7 @@ export default (props: AssistantProps) => {
     setConversationList,
     loadMoreConversation,
     hasMoreConversation,
-    refreshConversationList
+    refreshConversationList,
   } = useChat({
     app_name: props.agent?.key || "",
     onSuccess: (msg) => {
@@ -142,6 +146,24 @@ export default (props: AssistantProps) => {
     },
   });
 
+  useEffect(() => {
+    setAttachmentItems([])
+    setFileIds([]);
+    setOpenAttachment(false);
+  }, [activeConversation]);
+
+  useEffect(() => {
+    // 清除上传文件
+    setAttachmentItems([])
+    setFileIds([]);
+    setOpenAttachment(false);
+    // 查询是否支持文件
+    props.agent?.key &&
+      AppParamenters({ app_name: props.agent.key }).then((res) => {
+        setSupportFile(!!res?.result?.file_upload.enabled);
+      });
+  }, [props.agent?.key]);
+
   const handleChangeConversationName = (conversation: Conversation) => {
     let new_name: string;
     Modal.info({
@@ -190,7 +212,7 @@ export default (props: AssistantProps) => {
           session_id: conversation.key,
         }).then(() => {
           message.success("删除成功");
-          refreshConversationList()
+          refreshConversationList();
 
           addConversation();
         });
@@ -199,7 +221,7 @@ export default (props: AssistantProps) => {
   };
 
   const menuConfig: ConversationsProps["menu"] = (conversation) => {
-    if (conversation.key === "1") return undefined;
+    if (conversation.key === "1") return { items: [] }; // 不显示菜单
     return {
       items: [
         {
@@ -250,6 +272,41 @@ export default (props: AssistantProps) => {
     };
   }, []);
 
+  type UploadFileType = {
+    uid?: string;
+    name: string;
+  } & File;
+
+  // 上传文件
+  const uploadRequest: AttachmentsProps["customRequest"] = async (options) => {
+    const { file, onSuccess, onError } = options;
+    if (!supportFile) {
+      message.error("当前应用不支持文件上传");
+      return;
+    }
+    if (!file) {
+      message.error("请选择文件");
+      return;
+    }
+
+    const formData = new FormData();
+    formData.append("file", file);
+    try {
+      const res = await UploadFile(formData);
+      if(res.code === 1) {
+        onSuccess?.(res.result);
+        const uid = (file as UploadFileType).uid || Date.now().toString();
+        res.result?.[0] && setFileIds((ids) => [...ids, {[uid]: res.result[0].id}]);
+      } else {
+        onError?.(res?.message || "上传失败");
+      }
+    } catch (error) {
+      console.error("上传文件失败:", error);
+      onError?.(error as any);
+      return;
+    }
+  };
+
   // 附件组件
   const senderHeader = (
     <Sender.Header
@@ -265,7 +322,7 @@ export default (props: AssistantProps) => {
     >
       <Attachments
         ref={attachmentsRef}
-        beforeUpload={() => false}
+        // beforeUpload={() => false}
         items={attachmentItems}
         onChange={({ fileList }) => setAttachmentItems(fileList)}
         placeholder={(type) =>
@@ -280,6 +337,12 @@ export default (props: AssistantProps) => {
               }
         }
         getDropContainer={() => senderRef.current?.nativeElement}
+        customRequest={uploadRequest}
+        onRemove={(file) => {
+          setFileIds((arr) => arr.filter((item) => {
+            return !Object.keys(item)?.includes(file.uid)
+          }));
+        }}
       />
     </Sender.Header>
   );
@@ -334,7 +397,12 @@ export default (props: AssistantProps) => {
         },
       ];
     });
-    onRequest(msg);
+    const ids = supportFile ? fileIds.map(item => Object.values(item)[0]).flat(Infinity) : undefined;
+    onRequest(msg, ids);
+
+    setFileIds([]);
+    setAttachmentItems([]);
+    setOpenAttachment(false);
   };
 
   // 点击提示词
@@ -353,6 +421,10 @@ export default (props: AssistantProps) => {
       },
     ]);
     onRequest(msg);
+
+    setAttachmentItems([])
+    setFileIds([]);
+    setOpenAttachment(false);
   };
 
   // 停止对话
@@ -419,7 +491,13 @@ export default (props: AssistantProps) => {
                       <Spin indicator={<RedoOutlined spin />} size="small" />
                     </div>
                   }
-                  endMessage={<Divider plain><span className="text-12px text-text-secondary">无更多会话~</span></Divider>}
+                  endMessage={
+                    <Divider plain>
+                      <span className="text-12px text-text-secondary">
+                        无更多会话~
+                      </span>
+                    </Divider>
+                  }
                   scrollableTarget="scrollableDiv"
                   style={{ overflow: "hidden" }}
                 >
@@ -474,7 +552,7 @@ export default (props: AssistantProps) => {
                   </>
                 ) : (
                   <Bubble.List
-                    style={{ maxHeight: contentHeight, padding: '0 20px' }}
+                    style={{ maxHeight: contentHeight, padding: "0 20px" }}
                     autoScroll
                     roles={roles}
                     items={messages}
@@ -513,13 +591,15 @@ export default (props: AssistantProps) => {
                       header={senderHeader}
                       loading={loading}
                       prefix={
-                        <Button
-                          type="text"
-                          icon={<LinkOutlined />}
-                          onClick={() => {
-                            setOpenAttachment(!openAttachment);
-                          }}
-                        />
+                        supportFile && (
+                          <Button
+                            type="text"
+                            icon={<LinkOutlined />}
+                            onClick={() => {
+                              setOpenAttachment(!openAttachment);
+                            }}
+                          />
+                        )
                       }
                       value={senderVal}
                       onPasteFile={(file) => {

+ 13 - 2
src/pages/application/index.tsx

@@ -137,10 +137,21 @@ export default function Home() {
         <div className="relative flex flex-1 pb-6 flex-col overflow-auto bg-gray-100 shrink-0 grow mt-4 relative">
           <nav className="style_appList grid content-start shrink-0 gap-4 px-6 sm:px-12">
             {(data?.result?.model || []).map((item: any, index: number) => (
-              <ItemCard data={item} key={index} onClick={handleToAppDetail} />
+              <ItemCard
+                data={item}
+                key={index}
+                onClick={handleToAppDetail}
+                extra={
+                  item?.isCanUseTrial && (
+                    <div className="absolute bg-#0e53e2 right--30px top--2px text-10px text-#fff text-center w-80px rotate-34deg">
+                      试
+                    </div>
+                  )
+                }
+              />
             ))}
           </nav>
-          
+
           <Pagination
             className="mt-6"
             align="center"

+ 5 - 4
src/pages/detail/index.tsx

@@ -101,13 +101,13 @@ export default function detail() {
                 {data?.result?.desc || "暂无描述"}
               </div>
               <div className="mt-12px flex items-center">
-                <div className="block bg-[#dcdde0] rounded-8px text-secondary px-8px py-4px text-12px">
+                {/* <div className="block bg-[#dcdde0] rounded-8px text-secondary px-8px py-4px text-12px">
                   <i className="iconfont icon-qiyexinxi mr-8px" />
                   <span>{data?.result?.createByName || "易码工坊"}</span>
-                </div>
+                </div> */}
                 <div className="flex items-center px-[14px] leading-24px text-12px">
                   <div className="flex items-center space-x-1 text-text-tertiary">
-                    <svg
+                    {/* <svg
                       viewBox="0 0 24 24"
                       xmlns="http://www.w3.org/2000/svg"
                       width="24"
@@ -116,7 +116,8 @@ export default function detail() {
                       className="remixicon shrink-0 w-3 h-3"
                     >
                       <path d="M9 2V4H5L4.999 14H18.999L19 4H15V2H20C20.5523 2 21 2.44772 21 3V21C21 21.5523 20.5523 22 20 22H4C3.44772 22 3 21.5523 3 21V3C3 2.44772 3.44772 2 4 2H9ZM18.999 16H4.999L5 20H19L18.999 16ZM17 17V19H15V17H17ZM13 2V7H16L12 11L8 7H11V2H13Z"></path>
-                    </svg>
+                    </svg> */}
+                    <i className="iconfont icon-liulanliang"/>
                     <div className="system-xs-regular">浏览量 {data?.result?.viewCount || 0}</div>
                   </div>
                   <div className="mx-2 text-text-quaternary system-xs-regular">