aboutsummaryrefslogtreecommitdiffstats
path: root/desktop/assistant/Files.js
blob: a75e69377cc746a5e41ae73072d166ab2baf854f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
// Copyright (C) 2026 Danilo M. <danix@danix.xyz>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

.pragma library

// What a staged file is, for its chip and the actions offered on it. The
// text and image lists match desktop-assistant's, which decides what chat
// accepts; this only decides what the card offers.
// A suffix not listed here reads as "other" at first; Assistant.qml then
// asks file(1) and fromMime() gives the kind.
// Text splits into prose and data (text) and code or configuration (code),
// so each gets the presets that fit it; the daemon sees one text kind.
const TEXT = [".md", ".rst", ".txt", ".csv"];
const CODE = [".py", ".c", ".h", ".cpp", ".hpp", ".rs", ".go", ".js", ".ts", ".jsx",
              ".tsx", ".sh", ".bash", ".zsh", ".lua", ".rb", ".pl", ".php", ".java",
              ".kt", ".swift", ".sql", ".toml", ".yaml", ".yml", ".json", ".ini",
              ".cfg", ".conf", ".xml", ".html", ".css",
              ".diff", ".patch", ".slackbuild", ".info", ".desktop", ".service", ".qml"];
const IMAGE = [".png", ".jpg", ".jpeg", ".gif", ".webp"];
const VIDEO = [".mp4", ".mkv", ".webm", ".mov", ".avi", ".m4v"];
const ARCHIVE = [".zip", ".tar", ".gz", ".tgz", ".xz", ".txz", ".bz2", ".zst", ".7z", ".rar"];

// Kinds chat takes; the rest only get local actions.
const CHAT = ["text", "code", "image", "pdf"];

function name(path) { return path.slice(path.lastIndexOf("/") + 1); }

function kind(path) {
    const n = name(path).toLowerCase();
    const dot = n.lastIndexOf(".");
    const ext = dot > 0 ? n.slice(dot) : "";
    if (ext === ".pdf") return "pdf";
    if (IMAGE.includes(ext)) return "image";
    if (VIDEO.includes(ext)) return "video";
    if (ARCHIVE.includes(ext)) return "archive";
    if (TEXT.includes(ext)) return "text";
    if (CODE.includes(ext)) return "code";
    return "other";
}

function chats(k) { return CHAT.includes(k); }

// For a file the name says nothing about (a script with no suffix), from
// the mime type `file -b --mime-type` reports. Prose and data are text;
// any other text type, scripts and source among them, is code.
const ARCHIVE_MIMES = ["application/zip", "application/x-tar", "application/gzip",
                       "application/x-xz", "application/x-bzip2", "application/zstd",
                       "application/x-7z-compressed", "application/x-rar",
                       "application/vnd.rar"];

function fromMime(m) {
    if (["text/plain", "text/markdown", "text/csv"].includes(m)) return "text";
    if (m.startsWith("text/") || ["application/json", "application/xml",
                                  "application/javascript"].includes(m)) return "code";
    if (["image/png", "image/jpeg", "image/gif", "image/webp"].includes(m)) return "image";
    if (m === "application/pdf") return "pdf";
    if (m.startsWith("video/")) return "video";
    if (ARCHIVE_MIMES.includes(m)) return "archive";
    return "other";
}

// A dropped URL to a local path, or "" for anything that is not a local
// file: file:///path, or file://localhost/path; any other host is remote.
function fromUrl(url) {
    const m = /^file:\/\/(localhost)?(\/.*)$/.exec(String(url));
    if (!m) return "";
    try { return decodeURIComponent(m[2]); } catch (e) { return ""; }
}

function file(path) { return { path: path, name: name(path), kind: kind(path) }; }