|
| 1 | +import type { |
| 2 | + APIFileItem, |
| 3 | + ApiFileReadContentResponse, |
| 4 | + ApiDatasetDetailResponse, |
| 5 | + FeishuPrivateServer |
| 6 | +} from '@fastgpt/global/core/dataset/apiDataset'; |
| 7 | +import { type ParentIdType } from '@fastgpt/global/common/parentFolder/type'; |
| 8 | +import axios, { type Method } from 'axios'; |
| 9 | +import { addLog } from '../../../common/system/log'; |
| 10 | +import { preLoadWorker } from 'worker/preload'; |
| 11 | + |
| 12 | +type ResponseDataType = { |
| 13 | + success: boolean; |
| 14 | + message: string; |
| 15 | + data: any; |
| 16 | +}; |
| 17 | + |
| 18 | +type FeishuFileListResponse = { |
| 19 | + files: { |
| 20 | + token: string; |
| 21 | + parent_token: string; |
| 22 | + name: string; |
| 23 | + type: string; |
| 24 | + modified_time: number; |
| 25 | + created_time: number; |
| 26 | + url: string; |
| 27 | + owner_id: string; |
| 28 | + shortcut_info?: { |
| 29 | + target_token: string; |
| 30 | + target_type: string; |
| 31 | + }; |
| 32 | + }[]; |
| 33 | + has_more: boolean; |
| 34 | + next_page_token: string; |
| 35 | +}; |
| 36 | + |
| 37 | +type FeishuFileDetailResponse = { |
| 38 | + code: number; |
| 39 | + msg: string; |
| 40 | + data: { |
| 41 | + name: string; |
| 42 | + parentId: string; |
| 43 | + }; |
| 44 | +}; |
| 45 | + |
| 46 | +const feishuBaseUrl = process.env.FEISHU_BASE_URL || 'https://open.feishu.cn'; |
| 47 | + |
| 48 | +export const useFeishuPrivateDatasetRequest = ({ |
| 49 | + feishuPrivateServer |
| 50 | +}: { |
| 51 | + feishuPrivateServer: FeishuPrivateServer; |
| 52 | +}) => { |
| 53 | + const instance = axios.create({ |
| 54 | + baseURL: feishuBaseUrl, |
| 55 | + timeout: 60000 |
| 56 | + }); |
| 57 | + |
| 58 | + instance.defaults.headers.common['Authorization'] = |
| 59 | + `Bearer ${feishuPrivateServer.user_access_token}`; |
| 60 | + instance.defaults.headers.common['Content-Type'] = 'application/json; charset=utf-8'; |
| 61 | + |
| 62 | + /** |
| 63 | + * 响应数据检查 |
| 64 | + */ |
| 65 | + const checkRes = (data: ResponseDataType) => { |
| 66 | + if (data === undefined) { |
| 67 | + addLog.info('yuque dataset data is empty'); |
| 68 | + return Promise.reject('服务器异常'); |
| 69 | + } |
| 70 | + return data.data; |
| 71 | + }; |
| 72 | + const responseError = (err: any) => { |
| 73 | + console.log('error->', '请求错误', err); |
| 74 | + |
| 75 | + if (!err) { |
| 76 | + return Promise.reject({ message: '未知错误' }); |
| 77 | + } |
| 78 | + if (typeof err === 'string') { |
| 79 | + return Promise.reject({ message: err }); |
| 80 | + } |
| 81 | + if (typeof err.message === 'string') { |
| 82 | + return Promise.reject({ message: err.message }); |
| 83 | + } |
| 84 | + if (typeof err.data === 'string') { |
| 85 | + return Promise.reject({ message: err.data }); |
| 86 | + } |
| 87 | + if (err?.response?.data) { |
| 88 | + return Promise.reject(err?.response?.data); |
| 89 | + } |
| 90 | + return Promise.reject(err); |
| 91 | + }; |
| 92 | + |
| 93 | + const request = <T>(url: string, data: any, method: Method): Promise<T> => { |
| 94 | + /* 去空 */ |
| 95 | + for (const key in data) { |
| 96 | + if (data[key] === undefined) { |
| 97 | + delete data[key]; |
| 98 | + } |
| 99 | + } |
| 100 | + |
| 101 | + return instance |
| 102 | + .request({ |
| 103 | + url, |
| 104 | + method, |
| 105 | + data: ['POST', 'PUT'].includes(method) ? data : undefined, |
| 106 | + params: !['POST', 'PUT'].includes(method) ? data : undefined |
| 107 | + }) |
| 108 | + .then((res) => checkRes(res.data)) |
| 109 | + .catch((err) => responseError(err)); |
| 110 | + }; |
| 111 | + |
| 112 | + const listFiles = async ({ parentId }: { parentId?: ParentIdType }): Promise<APIFileItem[]> => { |
| 113 | + const fetchFiles = async ( |
| 114 | + pageToken?: string, |
| 115 | + parentId?: ParentIdType |
| 116 | + ): Promise<FeishuFileListResponse['files']> => { |
| 117 | + const data = await request<FeishuFileListResponse>( |
| 118 | + `/open-apis/drive/v1/files`, |
| 119 | + { |
| 120 | + page_size: 200, |
| 121 | + page_token: pageToken, |
| 122 | + folder_token: parentId ? parentId : undefined |
| 123 | + }, |
| 124 | + 'GET' |
| 125 | + ); |
| 126 | + if (data.has_more) { |
| 127 | + const nextFiles = await fetchFiles(data.next_page_token); |
| 128 | + return [...data.files, ...nextFiles]; |
| 129 | + } |
| 130 | + |
| 131 | + return data.files; |
| 132 | + }; |
| 133 | + const parent = parentId?.split('-')[1]; |
| 134 | + |
| 135 | + const allFiles = await fetchFiles(undefined, parent); |
| 136 | + |
| 137 | + return allFiles |
| 138 | + .filter((file) => { |
| 139 | + if (file.type === 'shortcut') { |
| 140 | + return ( |
| 141 | + file.shortcut_info?.target_type === 'docx' || |
| 142 | + file.shortcut_info?.target_type === 'folder' |
| 143 | + ); |
| 144 | + } |
| 145 | + return file.type === 'folder' || file.type === 'docx'; |
| 146 | + }) |
| 147 | + .map((file) => ({ |
| 148 | + id: |
| 149 | + file.type === 'shortcut' |
| 150 | + ? file.parent_token + '-' + file.shortcut_info!.target_token |
| 151 | + : file.parent_token + '-' + file.token, |
| 152 | + parentId: parentId, |
| 153 | + name: file.name, |
| 154 | + type: file.type === 'folder' ? ('folder' as const) : ('file' as const), |
| 155 | + hasChild: file.type === 'folder', |
| 156 | + updateTime: new Date(file.modified_time * 1000), |
| 157 | + createTime: new Date(file.created_time * 1000) |
| 158 | + })); |
| 159 | + }; |
| 160 | + |
| 161 | + const getFileContent = async ({ |
| 162 | + apiFileId |
| 163 | + }: { |
| 164 | + apiFileId: string; |
| 165 | + }): Promise<ApiFileReadContentResponse> => { |
| 166 | + const fileId = apiFileId.split('-')[1]; |
| 167 | + const [{ content }, { document }] = await Promise.all([ |
| 168 | + request<{ content: string }>(`/open-apis/docx/v1/documents/${fileId}/raw_content`, {}, 'GET'), |
| 169 | + request<{ document: { title: string } }>(`/open-apis/docx/v1/documents/${fileId}`, {}, 'GET') |
| 170 | + ]); |
| 171 | + |
| 172 | + return { |
| 173 | + title: document?.title, |
| 174 | + rawText: content |
| 175 | + }; |
| 176 | + }; |
| 177 | + |
| 178 | + const getFilePreviewUrl = async ({ apiFileId }: { apiFileId: string }): Promise<string> => { |
| 179 | + const fileId = apiFileId.split('-')[1]; |
| 180 | + const { metas } = await request<{ metas: { url: string }[] }>( |
| 181 | + `/open-apis/drive/v1/metas/batch_query`, |
| 182 | + { |
| 183 | + request_docs: [ |
| 184 | + { |
| 185 | + doc_token: fileId, |
| 186 | + doc_type: 'docx' |
| 187 | + } |
| 188 | + ], |
| 189 | + with_url: true |
| 190 | + }, |
| 191 | + 'POST' |
| 192 | + ); |
| 193 | + |
| 194 | + return metas[0].url; |
| 195 | + }; |
| 196 | + |
| 197 | + const getFileDetail = async ({ |
| 198 | + apiFileId |
| 199 | + }: { |
| 200 | + apiFileId: string; |
| 201 | + }): Promise<ApiDatasetDetailResponse> => { |
| 202 | + const parentId = apiFileId.split('-')[0]; |
| 203 | + const fileId = apiFileId.split('-')[1]; |
| 204 | + |
| 205 | + const fileDetail = await request<FeishuFileDetailResponse['data']>( |
| 206 | + `/open-apis/drive/explorer/v2/folder/${fileId}/meta`, |
| 207 | + {}, |
| 208 | + 'GET' |
| 209 | + ); |
| 210 | + console.log('fileDetail', fileDetail); |
| 211 | + if (!fileDetail) { |
| 212 | + return { |
| 213 | + name: '', |
| 214 | + parentId: null, |
| 215 | + id: apiFileId |
| 216 | + }; |
| 217 | + } |
| 218 | + |
| 219 | + return { |
| 220 | + name: fileDetail?.name, |
| 221 | + parentId: null, |
| 222 | + id: apiFileId |
| 223 | + }; |
| 224 | + }; |
| 225 | + |
| 226 | + return { |
| 227 | + getFileContent, |
| 228 | + listFiles, |
| 229 | + getFilePreviewUrl, |
| 230 | + getFileDetail |
| 231 | + }; |
| 232 | +}; |
0 commit comments