import { useCallback, useEffect, useRef, useState } from 'react' import type { ReactElement, ReactNode } from 'react' import type { PropsLocale } from '@deepseek-ai/dsh-client-ui-slots' import * as primitives from '@deepseek-ai/dsh-client-ui-primitives' import { FileShareError, deliverFile, fileNameOf, canShareFiles, mimeTypeOf, readWholeFile, } from './file-share-core.ts' import type { ReadBytesResult, ShareOutcome } from './file-share-core.ts' import { FILE_SHARE_NS } from './file-share-locale.ts' import type { FileShareKey } from './file-share-locale.ts' /** * Host 0.2.0 slot contracts this feature contributes to (declared by * ui-sidebar-files and ui-sidebar-documentpreview; the repo's locked * dsh-client-ui-slots 0.1.0-rc.6 typings predate them). Both are session * list slots whose owner passes `{ absolutePath }` (files: the tree root; * document: the previewed file's Host path, rendered only once it is known). */ declare module '@deepseek-ai/dsh-client-ui-slots' { interface SlotMap { 'sidebar.right.tab.files.actions': { kind: 'list'; scope: 'session'; owner: { absolutePath: string } } 'sidebar.right.tab.document.actions': { kind: 'list'; scope: 'session'; owner: { absolutePath: string } } } } /** * Session-bound ranged read over the host's `remote.workspaceFiles.readBytes`. * @param sessionId - Session whose workspace authorizes the read. * @param path - absolute or workspace-relative path. * @param range - byte window. * @param signal - cancellation. */ export type ReadFileRange = ( sessionId: string, path: string, range: { offset: number; length: number }, signal: AbortSignal, ) => Promise /** Business face both share controls receive through their slot `inject`. */ export interface FileShareInject { readFileRange: ReadFileRange } /** Props both slot entries consume (a supertype of the composed slot props). */ export interface FileShareSlotProps extends FileShareInject, PropsLocale { /** Owner share: the tree root (files) or the previewed file (document). */ absolutePath: string /** Framework-resolved session id. */ sessionId: string } type Translate = (key: FileShareKey, params?: Record) => string /** Host icon / toast component shapes, typed locally (see core/icon-compat.ts for why). */ type HostIcon = (props: { size?: number; className?: string }) => ReactElement | null type HostToast = (props: { text: string; icon?: ReactNode; onDone: () => void }) => ReactElement | null const hostTable = primitives as unknown as Record const hostShareIcon = (['IconShareOutlineRegular', 'IconShareOutline16'] .map(name => hostTable[name]) .find(value => typeof value === 'function') as HostIcon | undefined) const hostWarningIcon = (['IconWarningOutlineRegular', 'IconWarningOutline16'] .map(name => hostTable[name]) .find(value => typeof value === 'function') as HostIcon | undefined) const HostToastComponent = typeof hostTable.Toast === 'function' ? hostTable.Toast as HostToast : undefined /** Inline share glyph for injected DOM rows and hosts without a share icon (currentColor). */ const SHARE_SVG = '' const DOWNLOAD_SVG = '' /** 平台没有 Web Share 时按钮只能"下载",图标也跟着换成下载方向(诚实语义)。 */ function DownloadGlyph(): ReactElement { return