@dsh-sparrow/dsh-file-manage 0.1.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/CHANGELOG.zh-CN.md +10 -0
- package/README.md +42 -0
- package/README.zh-CN.md +42 -0
- package/cordis.patch.yml +5 -0
- package/docs/images/panel.png +0 -0
- package/lib/client/FileManageDock.js +141 -0
- package/lib/client/api.js +50 -0
- package/lib/client/index.js +81 -0
- package/lib/client/quota.js +23 -0
- package/lib/client/styles.js +297 -0
- package/lib/client.js +705 -0
- package/lib/files.js +82 -0
- package/lib/host.js +157 -0
- package/lib/index.js +3 -0
- package/lib/types/client/FileManageDock.d.ts +20 -0
- package/lib/types/client/api.d.ts +19 -0
- package/lib/types/client/index.d.ts +17 -0
- package/lib/types/client/quota.d.ts +6 -0
- package/lib/types/client/styles.d.ts +66 -0
- package/lib/types/files.d.ts +54 -0
- package/lib/types/host.d.ts +10 -0
- package/lib/types/index.d.ts +3 -0
- package/package.json +75 -0
package/lib/files.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/** dsh-file-manage 纯逻辑:分页参数、行格式化、上游错误分类。 @module dsh-file-manage/files */
|
|
2
|
+
import { LlmError } from '@deepseek-ai/dsh-llm';
|
|
3
|
+
/** 官方自动上传文件名的前缀(llm-deepseek file-store.ts 的 OWNED_FILE_PREFIX)。 */
|
|
4
|
+
export const DSH_OWNED_FILE_PREFIX = 'dsh-';
|
|
5
|
+
/** 列表页大小(owner 拍板 2026-09-01;官方上限 1000)。 */
|
|
6
|
+
export const PAGE_SIZE = 20;
|
|
7
|
+
/** 总数统计每页拉取上限(官方 list 上限 1000)。 */
|
|
8
|
+
export const COUNT_PAGE_LIMIT = 1000;
|
|
9
|
+
/** 总数统计最多翻页数:官方配额 10000 个文件 ÷ 每页 1000 = 10 页,12 页兜底防配额口径变化。 */
|
|
10
|
+
export const MAX_COUNT_PAGES = 12;
|
|
11
|
+
/** 总数统计每页请求超时。 */
|
|
12
|
+
export const COUNT_PAGE_TIMEOUT_MS = 15_000;
|
|
13
|
+
/**
|
|
14
|
+
* 分页参数归一化:limit 钳到 [1, 1000](非法回退 PAGE_SIZE);order 非 asc 视为 desc;空 after 省略。
|
|
15
|
+
* 异常输入返回安全默认值,不抛。
|
|
16
|
+
*/
|
|
17
|
+
export function normalizePageQuery(query) {
|
|
18
|
+
// 只接受纯数字串(Number('') === 0 会把空串误判为 0,需先拦)。
|
|
19
|
+
const limitText = query.limit;
|
|
20
|
+
const limitRaw = limitText !== undefined && /^\d+$/u.test(limitText) ? Number(limitText) : Number.NaN;
|
|
21
|
+
const limit = Number.isInteger(limitRaw) ? Math.min(1000, Math.max(1, limitRaw)) : PAGE_SIZE;
|
|
22
|
+
const after = query.after !== undefined && query.after !== '' ? query.after : undefined;
|
|
23
|
+
return { ...after === undefined ? {} : { after }, limit, order: query.order === 'asc' ? 'asc' : 'desc' };
|
|
24
|
+
}
|
|
25
|
+
/** 人读大小(二进制单位):B / KiB / MiB / GiB;异常输入返回 '0 B'。 */
|
|
26
|
+
export function formatBytes(bytes) {
|
|
27
|
+
if (!Number.isFinite(bytes) || bytes < 0)
|
|
28
|
+
return '0 B';
|
|
29
|
+
if (bytes < 1024)
|
|
30
|
+
return `${Math.round(bytes)} B`;
|
|
31
|
+
let value = bytes;
|
|
32
|
+
for (const unit of ['KiB', 'MiB', 'GiB']) {
|
|
33
|
+
value /= 1024;
|
|
34
|
+
if (value < 1024 || unit === 'GiB') {
|
|
35
|
+
return `${value >= 100 ? String(Math.round(value)) : value.toFixed(1)} ${unit}`;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return '0 B';
|
|
39
|
+
}
|
|
40
|
+
/** 时间戳(Unix 秒)→ 本地可读时间「YYYY-MM-DD HH:mm」;异常输入返回 '—'。 */
|
|
41
|
+
export function formatTimestamp(unixSeconds) {
|
|
42
|
+
if (!Number.isFinite(unixSeconds) || unixSeconds < 0)
|
|
43
|
+
return '—';
|
|
44
|
+
const date = new Date(unixSeconds * 1000);
|
|
45
|
+
const pad = (value) => String(value).padStart(2, '0');
|
|
46
|
+
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
|
47
|
+
}
|
|
48
|
+
/** 官方文件对象 → 面板行(格式化与 dsh- 判定集中在此,可单测)。 */
|
|
49
|
+
export function toFileRow(file) {
|
|
50
|
+
return {
|
|
51
|
+
id: file.id,
|
|
52
|
+
filename: file.filename,
|
|
53
|
+
bytes: file.bytes,
|
|
54
|
+
sizeLabel: formatBytes(file.bytes),
|
|
55
|
+
createdAtLabel: formatTimestamp(file.createdAt),
|
|
56
|
+
...file.expiresAt === undefined ? {} : { expiresAtLabel: formatTimestamp(file.expiresAt) },
|
|
57
|
+
dshOwned: file.filename.startsWith(DSH_OWNED_FILE_PREFIX),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/** 解码 DELETE 的文件 id 参数:畸形百分号编码返回空串(交由 400 分支),不抛 URIError。 */
|
|
61
|
+
export function decodeFileIdParam(raw) {
|
|
62
|
+
try {
|
|
63
|
+
return decodeURIComponent(raw);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return '';
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/** 官方 Files 客户端错误(DeepSeekFilesError 继承 LlmError,共享 code 分类)→ HTTP 状态与用户可读文案。 */
|
|
70
|
+
export function classifyUpstreamError(error) {
|
|
71
|
+
if (error instanceof LlmError) {
|
|
72
|
+
switch (error.code) {
|
|
73
|
+
case 'AUTH': return { code: 'AUTH', status: 401, message: '鉴权失败:DeepSeek API key 无效或已失效' };
|
|
74
|
+
case 'RATE_LIMIT': return { code: 'RATE_LIMIT', status: 429, message: '触发 DeepSeek 限流,请稍后重试' };
|
|
75
|
+
case 'SERVER': return { code: 'SERVER', status: 502, message: 'DeepSeek 服务端错误,请稍后重试' };
|
|
76
|
+
case 'FILES_API': return { code: 'FILES_API', status: 400, message: error.message };
|
|
77
|
+
default: return { code: 'UPSTREAM', status: 502, message: '上游请求失败,请稍后重试' };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return { code: 'UPSTREAM', status: 502, message: '上游请求失败,请稍后重试' };
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=files.js.map
|
package/lib/host.js
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/** dsh-file-manage host half:DeepSeek Files API 云端文件清单 REST 路由(复用官方 DeepSeekFilesClient)。 */
|
|
2
|
+
import { credentialRef } from '@deepseek-ai/dsh-credentials';
|
|
3
|
+
import { DeepSeekFileId, DeepSeekFilesClient, MAX_STORED_FILE_BYTES, MAX_STORED_FILE_COUNT } from '@deepseek-ai/dsh-llm-deepseek';
|
|
4
|
+
import { classifyUpstreamError, COUNT_PAGE_LIMIT, COUNT_PAGE_TIMEOUT_MS, decodeFileIdParam, formatBytes, MAX_COUNT_PAGES, normalizePageQuery, toFileRow } from './files.js';
|
|
5
|
+
export const name = 'dsh-file-manage';
|
|
6
|
+
export const inject = ['webServer', 'credentials', 'settings'];
|
|
7
|
+
const PREFIX = '/api/file-manage';
|
|
8
|
+
/** 官方 adapter 同款回退:设置节缺省时依次回退环境变量与公共端点。 */
|
|
9
|
+
const DEFAULT_BASE_URL = 'https://api.deepseek.com';
|
|
10
|
+
const DEFAULT_API_KEY_ENV = 'DEEPSEEK_API_KEY';
|
|
11
|
+
const LLM_DEEPSEEK_NS = 'llm-deepseek';
|
|
12
|
+
/** 插件自身错误(面板可读的 code / message + HTTP 状态)。 */
|
|
13
|
+
class FileManageError extends Error {
|
|
14
|
+
code;
|
|
15
|
+
status;
|
|
16
|
+
constructor(code, message, status) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.code = code;
|
|
19
|
+
this.status = status;
|
|
20
|
+
this.name = 'FileManageError';
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* 只读官方 llm-deepseek 设置节(ctx.settings.describe 的已解析值:schema 默认 + 组合 base + 用户层)。
|
|
25
|
+
* 本插件只用 baseURL / apiKeyEnv 两个字段;字段缺失回退官方默认。
|
|
26
|
+
*/
|
|
27
|
+
function readLlmDeepseekSection(ctx) {
|
|
28
|
+
const descriptor = ctx.settings.describe().find(item => item.ns === LLM_DEEPSEEK_NS);
|
|
29
|
+
const value = descriptor?.value;
|
|
30
|
+
return {
|
|
31
|
+
...typeof value?.baseURL === 'string' && value.baseURL !== '' ? { baseURL: value.baseURL } : {},
|
|
32
|
+
...typeof value?.apiKeyEnv === 'string' && value.apiKeyEnv !== '' ? { apiKeyEnv: value.apiKeyEnv } : {},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/** 每请求解析连接事实(与官方 adapter 同路径):设置节 → ctx.credentials 解析 → 环境回退。 */
|
|
36
|
+
async function resolveConnection(ctx) {
|
|
37
|
+
const section = readLlmDeepseekSection(ctx);
|
|
38
|
+
const baseURL = section.baseURL ?? process.env.DEEPSEEK_BASE_URL ?? DEFAULT_BASE_URL;
|
|
39
|
+
const apiKeyEnv = section.apiKeyEnv ?? DEFAULT_API_KEY_ENV;
|
|
40
|
+
let credential;
|
|
41
|
+
try {
|
|
42
|
+
credential = await ctx.credentials.resolve(credentialRef(apiKeyEnv));
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
throw new FileManageError('INVALID_CONFIG', `apiKeyEnv 配置不是合法的凭据引用:${apiKeyEnv}`, 500);
|
|
46
|
+
}
|
|
47
|
+
if (credential === undefined) {
|
|
48
|
+
throw new FileManageError('MISSING_CREDENTIAL', `缺少凭据 ${apiKeyEnv}:请在 dsh 中配置 DeepSeek API key 后重试`, 401);
|
|
49
|
+
}
|
|
50
|
+
return { baseURL, apiKey: credential.value };
|
|
51
|
+
}
|
|
52
|
+
function isFileManageError(error) {
|
|
53
|
+
return error instanceof FileManageError;
|
|
54
|
+
}
|
|
55
|
+
function sendJson(res, status, payload) {
|
|
56
|
+
if (res.headersSent)
|
|
57
|
+
return;
|
|
58
|
+
const body = JSON.stringify(payload);
|
|
59
|
+
res.statusCode = status;
|
|
60
|
+
res.setHeader('content-type', 'application/json; charset=utf-8');
|
|
61
|
+
res.setHeader('content-length', Buffer.byteLength(body));
|
|
62
|
+
res.end(body);
|
|
63
|
+
}
|
|
64
|
+
function sendError(res, error) {
|
|
65
|
+
sendJson(res, error.status, { error: { code: error.code, message: error.message } });
|
|
66
|
+
}
|
|
67
|
+
function sendUpstreamError(res, error) {
|
|
68
|
+
const info = classifyUpstreamError(error);
|
|
69
|
+
sendJson(res, info.status, { error: { code: info.code, message: info.message } });
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* host half 入口:注册 prefix 路由(list / 单条删除),全部副作用挂在 apply 的 effect 上。
|
|
73
|
+
* @param ctx - DSH 插件上下文。
|
|
74
|
+
*/
|
|
75
|
+
export function apply(ctx) {
|
|
76
|
+
ctx.effect(() => ctx.webServer.register({
|
|
77
|
+
kind: 'prefix',
|
|
78
|
+
path: PREFIX,
|
|
79
|
+
handler: async (req, res) => {
|
|
80
|
+
const url = new URL(req.url ?? '/', 'http://localhost');
|
|
81
|
+
const pathname = url.pathname;
|
|
82
|
+
try {
|
|
83
|
+
if (req.method === 'GET' && pathname === `${PREFIX}/list`) {
|
|
84
|
+
const query = normalizePageQuery({
|
|
85
|
+
after: url.searchParams.get('after') ?? undefined,
|
|
86
|
+
limit: url.searchParams.get('limit') ?? undefined,
|
|
87
|
+
order: url.searchParams.get('order') ?? undefined,
|
|
88
|
+
});
|
|
89
|
+
const connection = await resolveConnection(ctx);
|
|
90
|
+
const client = new DeepSeekFilesClient({ baseURL: connection.baseURL, apiKey: connection.apiKey });
|
|
91
|
+
const page = await client.list({
|
|
92
|
+
...query.after === undefined ? {} : { after: DeepSeekFileId(query.after) },
|
|
93
|
+
limit: query.limit,
|
|
94
|
+
order: query.order,
|
|
95
|
+
});
|
|
96
|
+
sendJson(res, 200, {
|
|
97
|
+
items: page.data.map(toFileRow),
|
|
98
|
+
hasMore: page.hasMore,
|
|
99
|
+
...page.lastId === undefined ? {} : { lastId: page.lastId },
|
|
100
|
+
});
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (req.method === 'GET' && pathname === `${PREFIX}/count`) {
|
|
104
|
+
// 官方 list 无总数字段:翻到底累计(每页 1000,配额内最多 10 页,MAX_COUNT_PAGES 兜底)。
|
|
105
|
+
const connection = await resolveConnection(ctx);
|
|
106
|
+
const client = new DeepSeekFilesClient({ baseURL: connection.baseURL, apiKey: connection.apiKey });
|
|
107
|
+
let count = 0;
|
|
108
|
+
let totalBytes = 0;
|
|
109
|
+
let after;
|
|
110
|
+
for (let page = 0; page < MAX_COUNT_PAGES; page++) {
|
|
111
|
+
const result = await client.list({
|
|
112
|
+
...after === undefined ? {} : { after },
|
|
113
|
+
limit: COUNT_PAGE_LIMIT,
|
|
114
|
+
order: 'desc',
|
|
115
|
+
signal: AbortSignal.timeout(COUNT_PAGE_TIMEOUT_MS),
|
|
116
|
+
});
|
|
117
|
+
count += result.data.length;
|
|
118
|
+
for (const file of result.data)
|
|
119
|
+
totalBytes += file.bytes;
|
|
120
|
+
if (!result.hasMore || result.lastId === undefined)
|
|
121
|
+
break;
|
|
122
|
+
after = result.lastId;
|
|
123
|
+
}
|
|
124
|
+
sendJson(res, 200, {
|
|
125
|
+
count,
|
|
126
|
+
totalBytes,
|
|
127
|
+
totalBytesLabel: formatBytes(totalBytes),
|
|
128
|
+
quotaBytes: MAX_STORED_FILE_BYTES,
|
|
129
|
+
quotaBytesLabel: formatBytes(MAX_STORED_FILE_BYTES),
|
|
130
|
+
quotaCount: MAX_STORED_FILE_COUNT,
|
|
131
|
+
});
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (req.method === 'DELETE' && pathname === `${PREFIX}/files`) {
|
|
135
|
+
const id = decodeFileIdParam(url.searchParams.get('id') ?? '');
|
|
136
|
+
if (id === '') {
|
|
137
|
+
sendError(res, new FileManageError('BAD_REQUEST', '缺少文件 id', 400));
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const connection = await resolveConnection(ctx);
|
|
141
|
+
const client = new DeepSeekFilesClient({ baseURL: connection.baseURL, apiKey: connection.apiKey });
|
|
142
|
+
await client.delete(DeepSeekFileId(id));
|
|
143
|
+
sendJson(res, 200, { deleted: true, id });
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
sendError(res, new FileManageError('NOT_FOUND', '未知路由', 404));
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
if (isFileManageError(error))
|
|
150
|
+
sendError(res, error);
|
|
151
|
+
else
|
|
152
|
+
sendUpstreamError(res, error);
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
}));
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=host.js.map
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** 云端文件面板视图:入口按钮 + 弹窗(列表 / 翻页 / 删除确认 / 复制 / 配额条);样式与请求分别在 styles.ts / api.ts。 */
|
|
2
|
+
import type { PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
3
|
+
import type { TranslateNS } from '@deepseek-ai/dsh-client-locale/client';
|
|
4
|
+
import type { FileRow } from '../files.js';
|
|
5
|
+
import type { FileCountSummary } from './api.js';
|
|
6
|
+
export interface FileManageDockInjected {
|
|
7
|
+
listFiles: (after?: string) => Promise<{
|
|
8
|
+
rows: FileRow[];
|
|
9
|
+
hasMore: boolean;
|
|
10
|
+
lastId?: string;
|
|
11
|
+
}>;
|
|
12
|
+
deleteFile: (id: string) => Promise<void>;
|
|
13
|
+
countFiles: () => Promise<FileCountSummary>;
|
|
14
|
+
}
|
|
15
|
+
export type FileManageDockProps = PropsRuntime<'sidebar.footer.action'> & FileManageDockInjected & {
|
|
16
|
+
t: TranslateNS<'file-manage'>;
|
|
17
|
+
};
|
|
18
|
+
/** 面板状态:首屏 loading / 错误横幅 / 列表 + 加载更多 / 删除确认框(web 确认框替代原生 confirm)。 */
|
|
19
|
+
export declare function FileManageDock({ wide, listFiles, deleteFile, countFiles, t }: FileManageDockProps): import("react").JSX.Element;
|
|
20
|
+
//# sourceMappingURL=FileManageDock.d.ts.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** dsh-file-manage 客户端请求封装:host 路由的 fetch 层(超时 + 错误提取)。 @module dsh-file-manage/client/api */
|
|
2
|
+
import type { FileRow } from '../files.js';
|
|
3
|
+
/** 总数统计(含配额,供网盘式进度条)。 */
|
|
4
|
+
export interface FileCountSummary {
|
|
5
|
+
count: number;
|
|
6
|
+
totalBytes: number;
|
|
7
|
+
totalBytesLabel: string;
|
|
8
|
+
quotaBytes: number;
|
|
9
|
+
quotaBytesLabel: string;
|
|
10
|
+
quotaCount: number;
|
|
11
|
+
}
|
|
12
|
+
export declare function listApi(after?: string): Promise<{
|
|
13
|
+
rows: FileRow[];
|
|
14
|
+
hasMore: boolean;
|
|
15
|
+
lastId?: string;
|
|
16
|
+
}>;
|
|
17
|
+
export declare function countApi(): Promise<FileCountSummary>;
|
|
18
|
+
export declare function deleteApi(id: string): Promise<void>;
|
|
19
|
+
//# sourceMappingURL=api.d.ts.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-file-manage client half:sidebar footer 入口 + 云端文件弹窗。
|
|
3
|
+
* 请求封装见 api.ts、样式见 styles.ts、视图见 FileManageDock.tsx;客户端不直接碰任何文件或凭据。
|
|
4
|
+
*/
|
|
5
|
+
import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client';
|
|
6
|
+
export declare const inject: string[];
|
|
7
|
+
/**
|
|
8
|
+
* client half 入口:注册 locale 字典 + sidebar footer action。
|
|
9
|
+
* @param ctx - 浏览器侧 Cordis 上下文。
|
|
10
|
+
*/
|
|
11
|
+
export declare function apply(ctx: ClientContext): void;
|
|
12
|
+
export { FileManageDock } from './FileManageDock.js';
|
|
13
|
+
export type { FileManageDockInjected, FileManageDockProps } from './FileManageDock.js';
|
|
14
|
+
export { ensureFileManageStyles } from './styles.js';
|
|
15
|
+
export { countApi, deleteApi, listApi } from './api.js';
|
|
16
|
+
export type { FileCountSummary } from './api.js';
|
|
17
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** dsh-file-manage 客户端纯逻辑:配额进度条计算(不 import Node 模块)。 @module dsh-file-manage/client/quota */
|
|
2
|
+
/** 存储用量比例:钳到 [0, 1];used/quota 非正时返回 0(不除零)。 */
|
|
3
|
+
export declare function storageUsageRatio(usedBytes: number, quotaBytes: number): number;
|
|
4
|
+
/** 用量百分比文案(网盘风格自适应精度):≥10% 取整、≥1% 一位、≥0.1% 两位、≥0.01% 三位、更小四位——25GiB 配额下常见 0.01% 量级,避免人人 0.0%。 */
|
|
5
|
+
export declare function formatUsagePercent(ratio: number): string;
|
|
6
|
+
//# sourceMappingURL=quota.d.ts.map
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/** dsh-file-manage 客户端样式层:注入样式表 + 面板内联样式(官方设计 token;按 data 属性去重)。 @module dsh-file-manage/client/styles */
|
|
2
|
+
/** 注入触发键 / 面板 / 确认框样式;HMR / 重载按 data 属性去重不叠加。 */
|
|
3
|
+
export declare function ensureFileManageStyles(): void;
|
|
4
|
+
/** 面板与行内联样式(对齐官方 Settings / Archive 的面板几何)。 */
|
|
5
|
+
export declare const styles: {
|
|
6
|
+
readonly overlay: {
|
|
7
|
+
position: "fixed";
|
|
8
|
+
inset: number;
|
|
9
|
+
zIndex: number;
|
|
10
|
+
display: "flex";
|
|
11
|
+
alignItems: "center";
|
|
12
|
+
justifyContent: "center";
|
|
13
|
+
background: string;
|
|
14
|
+
backdropFilter: "var(--dsw-mask-blur)";
|
|
15
|
+
};
|
|
16
|
+
readonly panel: {
|
|
17
|
+
position: "relative";
|
|
18
|
+
display: "flex";
|
|
19
|
+
flexDirection: "column";
|
|
20
|
+
width: string;
|
|
21
|
+
maxHeight: string;
|
|
22
|
+
borderRadius: number;
|
|
23
|
+
overflow: "hidden";
|
|
24
|
+
padding: number;
|
|
25
|
+
background: string;
|
|
26
|
+
color: "var(--dsw-alias-label-primary, #1f2329)";
|
|
27
|
+
boxShadow: "var(--dsw-shadow-lv3, 0 12px 40px rgba(0,0,0,0.22))";
|
|
28
|
+
};
|
|
29
|
+
readonly body: {
|
|
30
|
+
flex: number;
|
|
31
|
+
minHeight: number;
|
|
32
|
+
overflow: "auto";
|
|
33
|
+
scrollbarGutter: "stable";
|
|
34
|
+
padding: string;
|
|
35
|
+
};
|
|
36
|
+
readonly row: {
|
|
37
|
+
display: "flex";
|
|
38
|
+
justifyContent: "space-between";
|
|
39
|
+
gap: number;
|
|
40
|
+
alignItems: "center";
|
|
41
|
+
padding: string;
|
|
42
|
+
borderBottom: string;
|
|
43
|
+
};
|
|
44
|
+
readonly actions: {
|
|
45
|
+
display: "flex";
|
|
46
|
+
gap: number;
|
|
47
|
+
alignItems: "center";
|
|
48
|
+
};
|
|
49
|
+
readonly title: {
|
|
50
|
+
overflow: "hidden";
|
|
51
|
+
textOverflow: "ellipsis";
|
|
52
|
+
whiteSpace: "nowrap";
|
|
53
|
+
};
|
|
54
|
+
readonly secondarySmall: {
|
|
55
|
+
color: "var(--dsw-alias-label-secondary, #6b7280)";
|
|
56
|
+
fontSize: number;
|
|
57
|
+
lineHeight: string;
|
|
58
|
+
};
|
|
59
|
+
readonly footerBar: {
|
|
60
|
+
flex: string;
|
|
61
|
+
display: "flex";
|
|
62
|
+
justifyContent: "center";
|
|
63
|
+
padding: string;
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
//# sourceMappingURL=styles.d.ts.map
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/** dsh-file-manage 纯逻辑:分页参数、行格式化、上游错误分类。 @module dsh-file-manage/files */
|
|
2
|
+
import type { DeepSeekFileObject } from '@deepseek-ai/dsh-llm-deepseek';
|
|
3
|
+
/** 官方自动上传文件名的前缀(llm-deepseek file-store.ts 的 OWNED_FILE_PREFIX)。 */
|
|
4
|
+
export declare const DSH_OWNED_FILE_PREFIX = "dsh-";
|
|
5
|
+
/** 列表页大小(owner 拍板 2026-09-01;官方上限 1000)。 */
|
|
6
|
+
export declare const PAGE_SIZE = 20;
|
|
7
|
+
/** 总数统计每页拉取上限(官方 list 上限 1000)。 */
|
|
8
|
+
export declare const COUNT_PAGE_LIMIT = 1000;
|
|
9
|
+
/** 总数统计最多翻页数:官方配额 10000 个文件 ÷ 每页 1000 = 10 页,12 页兜底防配额口径变化。 */
|
|
10
|
+
export declare const MAX_COUNT_PAGES = 12;
|
|
11
|
+
/** 总数统计每页请求超时。 */
|
|
12
|
+
export declare const COUNT_PAGE_TIMEOUT_MS = 15000;
|
|
13
|
+
/** 归一化后的分页参数。 */
|
|
14
|
+
export interface PageQuery {
|
|
15
|
+
after?: string;
|
|
16
|
+
limit: number;
|
|
17
|
+
order: 'asc' | 'desc';
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* 分页参数归一化:limit 钳到 [1, 1000](非法回退 PAGE_SIZE);order 非 asc 视为 desc;空 after 省略。
|
|
21
|
+
* 异常输入返回安全默认值,不抛。
|
|
22
|
+
*/
|
|
23
|
+
export declare function normalizePageQuery(query: {
|
|
24
|
+
after?: string;
|
|
25
|
+
limit?: string;
|
|
26
|
+
order?: string;
|
|
27
|
+
}): PageQuery;
|
|
28
|
+
/** 人读大小(二进制单位):B / KiB / MiB / GiB;异常输入返回 '0 B'。 */
|
|
29
|
+
export declare function formatBytes(bytes: number): string;
|
|
30
|
+
/** 时间戳(Unix 秒)→ 本地可读时间「YYYY-MM-DD HH:mm」;异常输入返回 '—'。 */
|
|
31
|
+
export declare function formatTimestamp(unixSeconds: number): string;
|
|
32
|
+
/** 面板文件行(bytes 为原始字节数,供面板删除后本地校正)。 */
|
|
33
|
+
export interface FileRow {
|
|
34
|
+
id: string;
|
|
35
|
+
filename: string;
|
|
36
|
+
bytes: number;
|
|
37
|
+
sizeLabel: string;
|
|
38
|
+
createdAtLabel: string;
|
|
39
|
+
expiresAtLabel?: string;
|
|
40
|
+
dshOwned: boolean;
|
|
41
|
+
}
|
|
42
|
+
/** 官方文件对象 → 面板行(格式化与 dsh- 判定集中在此,可单测)。 */
|
|
43
|
+
export declare function toFileRow(file: DeepSeekFileObject): FileRow;
|
|
44
|
+
/** 解码 DELETE 的文件 id 参数:畸形百分号编码返回空串(交由 400 分支),不抛 URIError。 */
|
|
45
|
+
export declare function decodeFileIdParam(raw: string): string;
|
|
46
|
+
/** 上游错误 → 面板可见的分类;未知错误归为 502。 */
|
|
47
|
+
export interface UpstreamErrorInfo {
|
|
48
|
+
code: string;
|
|
49
|
+
status: number;
|
|
50
|
+
message: string;
|
|
51
|
+
}
|
|
52
|
+
/** 官方 Files 客户端错误(DeepSeekFilesError 继承 LlmError,共享 code 分类)→ HTTP 状态与用户可读文案。 */
|
|
53
|
+
export declare function classifyUpstreamError(error: unknown): UpstreamErrorInfo;
|
|
54
|
+
//# sourceMappingURL=files.d.ts.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** dsh-file-manage host half:DeepSeek Files API 云端文件清单 REST 路由(复用官方 DeepSeekFilesClient)。 */
|
|
2
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
3
|
+
export declare const name = "dsh-file-manage";
|
|
4
|
+
export declare const inject: string[];
|
|
5
|
+
/**
|
|
6
|
+
* host half 入口:注册 prefix 路由(list / 单条删除),全部副作用挂在 apply 的 effect 上。
|
|
7
|
+
* @param ctx - DSH 插件上下文。
|
|
8
|
+
*/
|
|
9
|
+
export declare function apply(ctx: Context): void;
|
|
10
|
+
//# sourceMappingURL=host.d.ts.map
|
package/package.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dsh-sparrow/dsh-file-manage",
|
|
3
|
+
"description": "DSH web plugin: manage DeepSeek Files API cloud files from the sidebar — list with cursor pagination, delete, and copy file_id (reuses the official DeepSeekFilesClient, no extra credentials)",
|
|
4
|
+
"version": "0.1.0-alpha.1",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/peiyucn/dsh-sparrow.git",
|
|
9
|
+
"directory": "plugins/dsh-file-manage"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"main": "lib/index.js",
|
|
13
|
+
"types": "lib/types/index.d.ts",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./lib/types/index.d.ts",
|
|
17
|
+
"default": "./lib/index.js"
|
|
18
|
+
},
|
|
19
|
+
"./client": {
|
|
20
|
+
"types": "./lib/types/client/index.d.ts",
|
|
21
|
+
"default": "./lib/client.js"
|
|
22
|
+
},
|
|
23
|
+
"./package.json": "./package.json"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"lib/**/*.js",
|
|
27
|
+
"lib/types/**/*.d.ts",
|
|
28
|
+
"cordis.patch.yml",
|
|
29
|
+
"docs/images/**",
|
|
30
|
+
"README.md",
|
|
31
|
+
"README.zh-CN.md",
|
|
32
|
+
"CHANGELOG.md", "CHANGELOG.zh-CN.md"
|
|
33
|
+
],
|
|
34
|
+
"dsh": {
|
|
35
|
+
"bundle": {
|
|
36
|
+
"patch": "./cordis.patch.yml"
|
|
37
|
+
},
|
|
38
|
+
"client": {
|
|
39
|
+
"platform": "web",
|
|
40
|
+
"inject": [
|
|
41
|
+
"@deepseek-ai/dsh-client-runtime",
|
|
42
|
+
"@deepseek-ai/dsh-client-locale",
|
|
43
|
+
"@deepseek-ai/dsh-client-ui-sidebar"
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"verify": "node ../../scripts/verify.mjs"
|
|
49
|
+
},
|
|
50
|
+
"peerDependencies": {
|
|
51
|
+
"@deepseek-ai/cordis": "^0.1.1-rc.2 || ^4.0.1",
|
|
52
|
+
"@deepseek-ai/dsh-credentials": "^0.1.1-rc.2",
|
|
53
|
+
"@deepseek-ai/dsh-host-webserver": "^0.1.1-rc.2",
|
|
54
|
+
"@deepseek-ai/dsh-llm": "^0.1.1-rc.2",
|
|
55
|
+
"@deepseek-ai/dsh-llm-deepseek": "^0.1.1-rc.2",
|
|
56
|
+
"@deepseek-ai/dsh-settings": "^0.1.1-rc.2"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@deepseek-ai/cordis": "0.1.1-rc.2",
|
|
60
|
+
"@deepseek-ai/dsh-client-locale": "0.1.1-rc.2",
|
|
61
|
+
"@deepseek-ai/dsh-client-runtime": "0.1.1-rc.2",
|
|
62
|
+
"@deepseek-ai/dsh-client-ui-primitives": "0.1.1-rc.2",
|
|
63
|
+
"@deepseek-ai/dsh-client-ui-sidebar": "0.1.1-rc.2",
|
|
64
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.1.1-rc.2",
|
|
65
|
+
"@deepseek-ai/dsh-credentials": "0.1.1-rc.2",
|
|
66
|
+
"@deepseek-ai/dsh-host-webserver": "0.1.1-rc.2",
|
|
67
|
+
"@deepseek-ai/dsh-llm": "0.1.1-rc.2",
|
|
68
|
+
"@deepseek-ai/dsh-llm-deepseek": "0.1.1-rc.2",
|
|
69
|
+
"@deepseek-ai/dsh-settings": "0.1.1-rc.2",
|
|
70
|
+
"@types/node": "^22.20.0",
|
|
71
|
+
"@types/react": "~18.3.1",
|
|
72
|
+
"@types/react-dom": "~18.3.1",
|
|
73
|
+
"react": "^18.2.0"
|
|
74
|
+
}
|
|
75
|
+
}
|