mirror of
https://github.com/andy-stack/vaultkeeper-ai.git
synced 2026-07-22 06:42:03 +00:00
refactor: extract MIME type mappings and improve attachment handling
- Move MimeTypeToFileTypes and FileTypeToMimeType mappings to dedicated FileTypeMimeTypeMapping enum file - Add empty content validation for file attachments - Add PDF support for Gemini - Display attachment references in user messages with file info - Fix OpenAI unsupported mime type message formatting - Improve URI list handling in InputService to include text/plain - Update chat area padding and styling for better attachment display - Prevent drag selection on chat padding element - Update test assertions for new unsupported mime type message format
This commit is contained in:
parent
13cdd0a162
commit
ea9cd211fc
22 changed files with 451 additions and 289 deletions
|
|
@ -138,6 +138,9 @@ export abstract class BaseAIClass implements IAIClass {
|
|||
|
||||
for (const attachment of attachments) {
|
||||
try {
|
||||
if (attachment.base64.trim() === "") {
|
||||
Exception.throw("File has no content!");
|
||||
}
|
||||
await this.aiFileService.uploadFile(attachment);
|
||||
} catch (error) {
|
||||
Exception.log(`Failed to upload ${attachment.fileName}: ${Exception.messageFrom(error)}`);
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@ import { Role } from "Enums/Role";
|
|||
import type { RawMessageStreamEvent, ContentBlockParam, Tool } from '@anthropic-ai/sdk/resources/messages';
|
||||
import { Exception } from "Helpers/Exception";
|
||||
import { MimeType, toMimeType } from "Enums/MimeType";
|
||||
import { isTextFile, MimeTypeToFileTypes } from "Enums/FileType";
|
||||
import { isTextFile } from "Enums/FileType";
|
||||
import { MimeTypeToFileTypes } from "Enums/FileTypeMimeTypeMapping";
|
||||
|
||||
export class Claude extends BaseAIClass {
|
||||
|
||||
|
|
@ -286,7 +287,7 @@ export class Claude extends BaseAIClass {
|
|||
}
|
||||
|
||||
return [
|
||||
{type: "text", text: attachment.fileName},
|
||||
{type: "text", text: `Binary data for ${attachment.fileName} follows in next message` },
|
||||
{
|
||||
type: isPlainText || mimeType === MimeType.APPLICATION_PDF ? "document" : "image",
|
||||
source: {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@ import type { ConversationContent } from "Conversations/ConversationContent";
|
|||
import type { Candidate, Part, FunctionDeclaration } from "@google/genai";
|
||||
import { FinishReason } from "@google/genai";
|
||||
import { MimeType, toMimeType } from "Enums/MimeType";
|
||||
import { isTextFile, MimeTypeToFileTypes } from "Enums/FileType";
|
||||
import { isTextFile } from "Enums/FileType";
|
||||
import { MimeTypeToFileTypes } from "Enums/FileTypeMimeTypeMapping";
|
||||
|
||||
export class Gemini extends BaseAIClass {
|
||||
|
||||
|
|
@ -30,6 +31,8 @@ export class Gemini extends BaseAIClass {
|
|||
// Images
|
||||
MimeType.IMAGE_JPEG,
|
||||
MimeType.IMAGE_PNG,
|
||||
// PDF
|
||||
MimeType.APPLICATION_PDF,
|
||||
// Data Formats
|
||||
MimeType.APPLICATION_JSON,
|
||||
MimeType.APPLICATION_XML,
|
||||
|
|
@ -328,7 +331,7 @@ export class Gemini extends BaseAIClass {
|
|||
continue;
|
||||
}
|
||||
|
||||
parts.push({text: attachment.fileName});
|
||||
parts.push({ text: `Binary data for ${attachment.fileName} follows in next message` });
|
||||
parts.push({
|
||||
fileData: {
|
||||
mimeType: mimeType,
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@ import type { ResponseEvent, ResponseOutputTextDelta, ResponseOutputItemDone, Re
|
|||
import { Exception } from "Helpers/Exception";
|
||||
import { ApiErrorType } from "Types/ApiError";
|
||||
import { MimeType, toMimeType } from "Enums/MimeType";
|
||||
import { isTextFile, MimeTypeToFileTypes } from "Enums/FileType";
|
||||
import { isTextFile } from "Enums/FileType";
|
||||
import { MimeTypeToFileTypes } from "Enums/FileTypeMimeTypeMapping";
|
||||
|
||||
export class OpenAI extends BaseAIClass {
|
||||
|
||||
|
|
@ -332,14 +333,17 @@ export class OpenAI extends BaseAIClass {
|
|||
}
|
||||
|
||||
if (!isPlainText && !this.isSupportedMimeType(mimeType)) {
|
||||
contentBlocks.push([{ type: "input_text", text: `Unsupported mime type '${mimeType}': ${attachment.fileName}` }]);
|
||||
contentBlocks.push({ type: "input_text", text: `Unsupported mime type '${mimeType}': ${attachment.fileName}` });
|
||||
continue;
|
||||
}
|
||||
|
||||
contentBlocks.push({
|
||||
type: isPlainText || mimeType === MimeType.APPLICATION_PDF ? "input_file" : "input_image",
|
||||
file_id: fileID
|
||||
});
|
||||
contentBlocks.push(
|
||||
{ type: "input_text", text: `Binary data for ${attachment.fileName} follows in next message` },
|
||||
{
|
||||
type: isPlainText || mimeType === MimeType.APPLICATION_PDF ? "input_file" : "input_image",
|
||||
file_id: fileID
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return JSON.stringify([{
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@
|
|||
if (!shouldSettle) {
|
||||
padding = Math.max(padding, 25);
|
||||
}
|
||||
chatAreaPaddingElement.style.padding = `${Math.max(0, padding / 2)}px`;
|
||||
chatAreaPaddingElement.style.paddingBottom = `${Math.max(0, padding)}px`;
|
||||
|
||||
tick().then(() => {
|
||||
if (behavior && (autoScroll || shouldSettle)) {
|
||||
|
|
@ -259,16 +259,32 @@
|
|||
{#if message.role === Role.User}
|
||||
<div class="message-container {Role.User}" use:trackingAction={index}>
|
||||
<div class="message-bubble {Role.User}">
|
||||
<div class="message-text-user fade-in-fast" contenteditable="false">
|
||||
<div class="message-text-user content-fade-in" contenteditable="false">
|
||||
{@html content}
|
||||
</div>
|
||||
{#if message.references.length > 0}
|
||||
<div class="message-attachments-container">
|
||||
{#each message.references as reference}
|
||||
<div class="message-attachmanet" aria-label="{reference.fileName}">
|
||||
<div
|
||||
class="message-attachment-icon"
|
||||
|
||||
></div>
|
||||
<div class="message-attachment-info">
|
||||
<div class="message-attachment-name">{reference.fileName}</div>
|
||||
<div class="message-attachment-size">{reference.size}MB</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
{@const messageId = message.timestamp.getTime().toString()}
|
||||
<div class="message-container {Role.Assistant}" use:trackingAction={index}>
|
||||
<div class="message-bubble {Role.Assistant}">
|
||||
<div class="markdown-content fade-in-fast {currentStreamingMessageId === messageId ? "streaming" : ""}">
|
||||
<div class="markdown-content content-fade-in {currentStreamingMessageId === messageId ? "streaming" : ""}">
|
||||
{#if currentStreamingMessageId === messageId}
|
||||
<div use:streamingAction={messageId} class="streaming-content"></div>
|
||||
{:else}
|
||||
|
|
@ -290,7 +306,7 @@
|
|||
<CancellationIndicator/>
|
||||
{/if}
|
||||
|
||||
<div bind:this={chatAreaPaddingElement}></div>
|
||||
<div bind:this={chatAreaPaddingElement} style:user-select=none></div>
|
||||
|
||||
{#if messages.length === 0}
|
||||
<div class="conversation-empty-state">
|
||||
|
|
@ -372,7 +388,6 @@
|
|||
border: var(--border-width) solid var(--background-modifier-border);
|
||||
border-radius: var(--radius-m);
|
||||
padding: 0px var(--size-4-2);
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.message-bubble.assistant {
|
||||
|
|
@ -382,6 +397,7 @@
|
|||
|
||||
.message-text-user {
|
||||
margin: var(--size-4-2);
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.conversation-empty-state {
|
||||
|
|
@ -390,7 +406,6 @@
|
|||
font-size: var(--font-ui-medium);
|
||||
color: var(--text-muted);
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.streaming-content {
|
||||
|
|
@ -399,7 +414,7 @@
|
|||
}
|
||||
|
||||
/* Streaming message styles */
|
||||
.fade-in-fast {
|
||||
.content-fade-in {
|
||||
animation: reveal-fade 0.5s ease-in-out forwards;
|
||||
}
|
||||
|
||||
|
|
@ -413,4 +428,54 @@
|
|||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Message attachments styles */
|
||||
.message-attachments-container {
|
||||
display: flex;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
scroll-behavior: smooth;
|
||||
gap: var(--size-4-1);
|
||||
margin-bottom: var(--size-4-2);
|
||||
}
|
||||
|
||||
.message-attachments-container::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.message-attachmanet {
|
||||
display: grid;
|
||||
grid-template-rows: var(--size-4-1) auto var(--size-4-1);
|
||||
grid-template-columns: var(--size-4-1) auto var(--size-4-1) auto var(--size-4-1);
|
||||
border: var(--border-width) solid var(--background-modifier-border);
|
||||
border-radius: var(--radius-m);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.message-attachment-icon {
|
||||
grid-row: 2;
|
||||
grid-column: 2;
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
.message-attachment-info {
|
||||
grid-row: 2;
|
||||
grid-column: 4;
|
||||
min-width: 40px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.message-attachment-name {
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
font-size: var(--font-smaller);
|
||||
}
|
||||
|
||||
.message-attachment-size {
|
||||
padding: 0;
|
||||
font-size: var(--font-smallest);
|
||||
color: var(--text-muted);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
<script lang="ts">
|
||||
import type { Attachment } from "Conversations/Attachment";
|
||||
import { MimeTypeToFileTypes } from "Enums/FileTypeMimeTypeMapping";
|
||||
import { toMimeType } from "Enums/MimeType";
|
||||
import { setIcon } from "obsidian";
|
||||
import {
|
||||
|
|
@ -8,7 +9,6 @@
|
|||
isKnownFileType,
|
||||
isTextFile,
|
||||
isVideoFile,
|
||||
MimeTypeToFileTypes,
|
||||
} from "Enums/FileType";
|
||||
|
||||
export let attachments: Attachment[] = [];
|
||||
|
|
@ -133,7 +133,9 @@
|
|||
grid-template-columns: var(--size-4-2) auto var(--size-4-2) auto var(--size-4-2) auto var(--size-4-2);
|
||||
border: var(--border-width) solid var(--background-modifier-border);
|
||||
border-radius: var(--radius-m);
|
||||
max-width: 15vw;
|
||||
max-width: 33%;
|
||||
min-width: 110px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.chat-attachmanet.selected {
|
||||
|
|
|
|||
|
|
@ -257,7 +257,7 @@
|
|||
const newAttachments = files.filter(file => !attachments.some(attachment => attachment.base64 === file.base64));
|
||||
attachments = [...attachments, ...newAttachments];
|
||||
|
||||
if (!plainText) {
|
||||
if (!plainText || plainText.includes("obsidian:")) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import type { AIProvider } from "Enums/ApiProvider";
|
||||
|
||||
export class Attachment {
|
||||
|
||||
public fileName: string;
|
||||
public mimeType: string;
|
||||
public base64: string;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import type { AIFunctionResponse } from "AIClasses/FunctionDefinitions/AIFunctio
|
|||
import { Role } from "Enums/Role";
|
||||
import { AIFunction } from "Enums/AIFunction";
|
||||
import { isTextFile, toFileType } from "Enums/FileType";
|
||||
import { FileTypeToMimeType } from "Enums/MimeType";
|
||||
import { FileTypeToMimeType } from "Enums/FileTypeMimeTypeMapping";
|
||||
|
||||
export class Conversation {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { Role } from "Enums/Role";
|
||||
import { ApiErrorType } from "Types/ApiError";
|
||||
import type { Attachment } from "./Attachment";
|
||||
import type { Reference } from "./Reference";
|
||||
|
||||
type ConversationContentInit = {
|
||||
role: Role;
|
||||
|
|
@ -10,6 +11,7 @@ type ConversationContentInit = {
|
|||
functionCall?: string;
|
||||
functionResponse?: string;
|
||||
attachments?: Attachment[];
|
||||
references?: Reference[];
|
||||
shouldDisplayContent?: boolean;
|
||||
toolId?: string;
|
||||
thoughtSignature?: string;
|
||||
|
|
@ -24,6 +26,7 @@ export class ConversationContent {
|
|||
public functionCall: string | undefined;
|
||||
public functionResponse: string | undefined;
|
||||
public attachments: Attachment[];
|
||||
public references: Reference[];
|
||||
public shouldDisplayContent: boolean;
|
||||
public toolId: string | undefined;
|
||||
public thoughtSignature: string | undefined;
|
||||
|
|
@ -40,6 +43,7 @@ export class ConversationContent {
|
|||
* @param init.functionCall - JSON string of the function call data (only set for function/tool calls)
|
||||
* @param init.functionResponse - JSON string of the function call response data (only set for function/tool responses)
|
||||
* @param init.attachments - Array of file attachments associated with this message (defaults to empty array)
|
||||
* @param init.references - Array of file references, used to display attachment's to the user associated with attachments
|
||||
* @param init.shouldDisplayContent - Whether this content should be displayed in the UI (defaults to true, false for system-generated messages)
|
||||
* @param init.toolId - Unique identifier for tool calls/responses (used to match calls with their responses)
|
||||
* @param init.thoughtSignature - Gemini-specific thought signature for extended thinking
|
||||
|
|
@ -53,6 +57,7 @@ export class ConversationContent {
|
|||
this.functionCall = init.functionCall;
|
||||
this.functionResponse = init.functionResponse;
|
||||
this.attachments = init.attachments ?? [];
|
||||
this.references = init.references ?? [];
|
||||
this.shouldDisplayContent = init.shouldDisplayContent ?? true;
|
||||
this.toolId = init.toolId;
|
||||
this.thoughtSignature = init.thoughtSignature;
|
||||
|
|
@ -74,6 +79,7 @@ export class ConversationContent {
|
|||
functionCall?: string;
|
||||
functionResponse?: string;
|
||||
attachments?: unknown[];
|
||||
references?: unknown[];
|
||||
shouldDisplayContent?: boolean;
|
||||
toolId?: string;
|
||||
thoughtSignature?: string;
|
||||
|
|
@ -92,6 +98,7 @@ export class ConversationContent {
|
|||
(!("functionCall" in data) || typeof data.functionCall === "string") &&
|
||||
(!("functionResponse" in data) || typeof data.functionResponse === "string") &&
|
||||
(!("attachments" in data) || Array.isArray(data.attachments)) &&
|
||||
(!("references" in data) || Array.isArray(data.references)) &&
|
||||
(!("shouldDisplayContent" in data) || typeof data.shouldDisplayContent === "boolean") &&
|
||||
(!("toolId" in data) || typeof data.toolId === "string") &&
|
||||
(!("thoughtSignature" in data) || typeof data.thoughtSignature === "string") &&
|
||||
|
|
|
|||
24
Conversations/Reference.ts
Normal file
24
Conversations/Reference.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
export class Reference {
|
||||
|
||||
public fileName: string;
|
||||
public size: number;
|
||||
|
||||
public constructor(fileName: string, size: number) {
|
||||
this.fileName = fileName;
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
public static isReferenceData(this: void, data: unknown): data is {
|
||||
fileName: string;
|
||||
size: number;
|
||||
} {
|
||||
return (
|
||||
data !== null &&
|
||||
typeof data === "object" &&
|
||||
"fileName" in data &&
|
||||
"size" in data &&
|
||||
typeof data.fileName === "string" &&
|
||||
typeof data.size === "number"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,3 @@
|
|||
import { MimeType } from "./MimeType";
|
||||
|
||||
export enum FileType {
|
||||
// ----- Types officially supported by Obsidian -----
|
||||
MD = "md",
|
||||
|
|
@ -282,103 +280,4 @@ export function isVideoFile(extension: string) {
|
|||
|| isFileType(extension, FileType.MP4)
|
||||
|| isFileType(extension, FileType.OGV)
|
||||
|| isFileType(extension, FileType.WEBM_VIDEO);
|
||||
}
|
||||
|
||||
export const MimeTypeToFileTypes: Record<MimeType, FileType[]> = {
|
||||
// Text
|
||||
[MimeType.TEXT_PLAIN]: [FileType.BASE, FileType.TXT, FileType.TEXT, FileType.INI, FileType.CFG, FileType.CONF, FileType.ENV, FileType.PROPERTIES, FileType.LOG, FileType.GITIGNORE, FileType.GITATTRIBUTES, FileType.EDITORCONFIG, FileType.PRETTIERRC, FileType.ESLINTRC, FileType.BABELRC, FileType.NPMRC, FileType.YARNRC, FileType.DIFF, FileType.PATCH],
|
||||
[MimeType.TEXT_MARKDOWN]: [FileType.MD],
|
||||
[MimeType.TEXT_MD]: [FileType.MD],
|
||||
[MimeType.TEXT_HTML]: [FileType.HTML],
|
||||
[MimeType.TEXT_CSS]: [FileType.CSS],
|
||||
[MimeType.TEXT_CSV]: [FileType.CSV],
|
||||
[MimeType.TEXT_TSV]: [FileType.TSV],
|
||||
[MimeType.TEXT_JAVASCRIPT]: [FileType.JS, FileType.MJS, FileType.CJS],
|
||||
[MimeType.TEXT_TYPESCRIPT]: [FileType.TS],
|
||||
[MimeType.TEXT_JSX]: [FileType.JSX],
|
||||
[MimeType.TEXT_TSX]: [FileType.TSX],
|
||||
[MimeType.TEXT_SASS]: [FileType.SASS],
|
||||
[MimeType.TEXT_SCSS]: [FileType.SCSS],
|
||||
[MimeType.TEXT_VUE]: [FileType.VUE],
|
||||
[MimeType.TEXT_SVELTE]: [FileType.SVELTE],
|
||||
[MimeType.TEXT_PYTHON]: [FileType.PY],
|
||||
[MimeType.TEXT_RUBY]: [FileType.RB],
|
||||
[MimeType.TEXT_PHP]: [FileType.PHP],
|
||||
[MimeType.TEXT_JAVA]: [FileType.JAVA],
|
||||
[MimeType.TEXT_JAVA_SOURCE]: [FileType.JAVA],
|
||||
[MimeType.TEXT_C]: [FileType.C, FileType.H],
|
||||
[MimeType.TEXT_CSRC]: [FileType.C],
|
||||
[MimeType.TEXT_CHDR]: [FileType.H],
|
||||
[MimeType.TEXT_CPP]: [FileType.CPP, FileType.CC, FileType.CXX, FileType.HPP, FileType.HXX],
|
||||
[MimeType.TEXT_CPPSRC]: [FileType.CPP, FileType.CC, FileType.CXX],
|
||||
[MimeType.TEXT_CPPHDR]: [FileType.HPP, FileType.HXX],
|
||||
[MimeType.TEXT_CSHARP]: [FileType.CS],
|
||||
[MimeType.TEXT_GO]: [FileType.GO],
|
||||
[MimeType.TEXT_RUST]: [FileType.RS],
|
||||
[MimeType.TEXT_SWIFT]: [FileType.SWIFT],
|
||||
[MimeType.TEXT_KOTLIN]: [FileType.KT, FileType.KTS],
|
||||
[MimeType.TEXT_SCALA]: [FileType.SCALA],
|
||||
[MimeType.TEXT_R]: [FileType.R, FileType.R_UPPER],
|
||||
[MimeType.TEXT_JULIA]: [FileType.JL],
|
||||
[MimeType.TEXT_LUA]: [FileType.LUA],
|
||||
[MimeType.TEXT_PERL]: [FileType.PL, FileType.PM],
|
||||
[MimeType.TEXT_DART]: [FileType.DART],
|
||||
[MimeType.TEXT_SHELL]: [FileType.SH, FileType.BASH, FileType.ZSH, FileType.FISH],
|
||||
[MimeType.TEXT_SH]: [FileType.SH, FileType.BASH, FileType.ZSH, FileType.FISH],
|
||||
[MimeType.TEXT_BATCH]: [FileType.BAT, FileType.CMD],
|
||||
[MimeType.TEXT_POWERSHELL]: [FileType.PS1],
|
||||
[MimeType.TEXT_SQL]: [FileType.SQL],
|
||||
[MimeType.TEXT_GRAPHQL]: [FileType.GRAPHQL, FileType.GQL],
|
||||
[MimeType.TEXT_XML]: [FileType.XML],
|
||||
[MimeType.TEXT_YAML]: [FileType.YAML, FileType.YML],
|
||||
|
||||
// Application
|
||||
[MimeType.APPLICATION_JSON]: [FileType.CANVAS, FileType.JSON],
|
||||
[MimeType.APPLICATION_XML]: [FileType.XML],
|
||||
[MimeType.APPLICATION_PDF]: [FileType.PDF],
|
||||
[MimeType.APPLICATION_RTF]: [FileType.RTF],
|
||||
[MimeType.APPLICATION_YAML]: [FileType.YAML, FileType.YML],
|
||||
[MimeType.APPLICATION_TOML]: [FileType.TOML],
|
||||
[MimeType.APPLICATION_TEX]: [FileType.TEX],
|
||||
[MimeType.APPLICATION_LATEX]: [FileType.LATEX],
|
||||
[MimeType.APPLICATION_MAKEFILE]: [FileType.MAKEFILE, FileType.MK],
|
||||
[MimeType.APPLICATION_GRADLE]: [FileType.GRADLE],
|
||||
[MimeType.APPLICATION_DOCKERFILE]: [FileType.DOCKERFILE],
|
||||
[MimeType.APPLICATION_PYTHON_CODE]: [FileType.PY],
|
||||
[MimeType.APPLICATION_JAVASCRIPT]: [FileType.JS, FileType.MJS, FileType.CJS],
|
||||
[MimeType.APPLICATION_TYPESCRIPT]: [FileType.TS],
|
||||
[MimeType.APPLICATION_SH]: [FileType.SH, FileType.BASH, FileType.ZSH, FileType.FISH],
|
||||
|
||||
// Markup formats
|
||||
[MimeType.TEXT_RST]: [FileType.RST],
|
||||
[MimeType.TEXT_ASCIIDOC]: [FileType.ADOC, FileType.ASCIIDOC],
|
||||
[MimeType.TEXT_ORG]: [FileType.ORG],
|
||||
[MimeType.TEXT_TEXTILE]: [FileType.TEXTILE],
|
||||
|
||||
// Images
|
||||
[MimeType.IMAGE_AVIF]: [FileType.AVIF],
|
||||
[MimeType.IMAGE_BMP]: [FileType.BMP],
|
||||
[MimeType.IMAGE_GIF]: [FileType.GIF],
|
||||
[MimeType.IMAGE_JPEG]: [FileType.JPEG, FileType.JPG],
|
||||
[MimeType.IMAGE_PNG]: [FileType.PNG],
|
||||
[MimeType.IMAGE_SVG]: [FileType.SVG],
|
||||
[MimeType.IMAGE_WEBP]: [FileType.WEBP],
|
||||
|
||||
// Audio
|
||||
[MimeType.AUDIO_FLAC]: [FileType.FLAC],
|
||||
[MimeType.AUDIO_MP4]: [FileType.M4A],
|
||||
[MimeType.AUDIO_MPEG]: [FileType.MP3],
|
||||
[MimeType.AUDIO_OGG]: [FileType.OGG],
|
||||
[MimeType.AUDIO_WAV]: [FileType.WAV],
|
||||
[MimeType.AUDIO_WEBM]: [FileType.WEBM_AUDIO],
|
||||
|
||||
// Video
|
||||
[MimeType.VIDEO_3GPP]: [FileType.THREE_GP],
|
||||
[MimeType.VIDEO_MATROSKA]: [FileType.MKV],
|
||||
[MimeType.VIDEO_QUICKTIME]: [FileType.MOV],
|
||||
[MimeType.VIDEO_MP4]: [FileType.MP4],
|
||||
[MimeType.VIDEO_OGG]: [FileType.OGV],
|
||||
[MimeType.VIDEO_WEBM]: [FileType.WEBM, FileType.WEBM_VIDEO],
|
||||
|
||||
[MimeType.UNKNOWN]: [FileType.UNKNOWN]
|
||||
};
|
||||
}
|
||||
249
Enums/FileTypeMimeTypeMapping.ts
Normal file
249
Enums/FileTypeMimeTypeMapping.ts
Normal file
|
|
@ -0,0 +1,249 @@
|
|||
import { FileType } from "./FileType";
|
||||
import { MimeType } from "./MimeType";
|
||||
|
||||
export const FileTypeToMimeType: Record<FileType, MimeType> = {
|
||||
// ----- Types officially supported by Obsidian -----
|
||||
[FileType.MD]: MimeType.TEXT_MARKDOWN,
|
||||
[FileType.BASE]: MimeType.TEXT_PLAIN,
|
||||
[FileType.CANVAS]: MimeType.APPLICATION_JSON,
|
||||
|
||||
// images
|
||||
[FileType.AVIF]: MimeType.IMAGE_AVIF,
|
||||
[FileType.BMP]: MimeType.IMAGE_BMP,
|
||||
[FileType.GIF]: MimeType.IMAGE_GIF,
|
||||
[FileType.JPEG]: MimeType.IMAGE_JPEG,
|
||||
[FileType.JPG]: MimeType.IMAGE_JPEG,
|
||||
[FileType.PNG]: MimeType.IMAGE_PNG,
|
||||
[FileType.SVG]: MimeType.IMAGE_SVG,
|
||||
[FileType.WEBP]: MimeType.IMAGE_WEBP,
|
||||
|
||||
// Note: WEBM is used for both audio and video, defaulting to video
|
||||
[FileType.WEBM]: MimeType.VIDEO_WEBM,
|
||||
|
||||
// audio
|
||||
[FileType.FLAC]: MimeType.AUDIO_FLAC,
|
||||
[FileType.M4A]: MimeType.AUDIO_MP4,
|
||||
[FileType.MP3]: MimeType.AUDIO_MPEG,
|
||||
[FileType.OGG]: MimeType.AUDIO_OGG,
|
||||
[FileType.WAV]: MimeType.AUDIO_WAV,
|
||||
[FileType.THREE_GP]: MimeType.VIDEO_3GPP,
|
||||
|
||||
// video
|
||||
[FileType.MKV]: MimeType.VIDEO_MATROSKA,
|
||||
[FileType.MOV]: MimeType.VIDEO_QUICKTIME,
|
||||
[FileType.MP4]: MimeType.VIDEO_MP4,
|
||||
[FileType.OGV]: MimeType.VIDEO_OGG,
|
||||
|
||||
// pdf
|
||||
[FileType.PDF]: MimeType.APPLICATION_PDF,
|
||||
|
||||
// ----- Types not officially supported by Obsidian -----
|
||||
// Plain text
|
||||
[FileType.TXT]: MimeType.TEXT_PLAIN,
|
||||
[FileType.TEXT]: MimeType.TEXT_PLAIN,
|
||||
|
||||
// Data formats
|
||||
[FileType.JSON]: MimeType.APPLICATION_JSON,
|
||||
[FileType.XML]: MimeType.APPLICATION_XML,
|
||||
[FileType.CSV]: MimeType.TEXT_CSV,
|
||||
[FileType.TSV]: MimeType.TEXT_TSV,
|
||||
[FileType.YAML]: MimeType.APPLICATION_YAML,
|
||||
[FileType.YML]: MimeType.APPLICATION_YAML,
|
||||
[FileType.TOML]: MimeType.APPLICATION_TOML,
|
||||
|
||||
// Web languages
|
||||
[FileType.HTML]: MimeType.TEXT_HTML,
|
||||
[FileType.CSS]: MimeType.TEXT_CSS,
|
||||
[FileType.SASS]: MimeType.TEXT_SASS,
|
||||
[FileType.SCSS]: MimeType.TEXT_SCSS,
|
||||
|
||||
// JavaScript/TypeScript ecosystem
|
||||
[FileType.JS]: MimeType.TEXT_JAVASCRIPT,
|
||||
[FileType.MJS]: MimeType.TEXT_JAVASCRIPT,
|
||||
[FileType.CJS]: MimeType.TEXT_JAVASCRIPT,
|
||||
[FileType.TS]: MimeType.TEXT_TYPESCRIPT,
|
||||
[FileType.JSX]: MimeType.TEXT_JSX,
|
||||
[FileType.TSX]: MimeType.TEXT_TSX,
|
||||
[FileType.VUE]: MimeType.TEXT_VUE,
|
||||
[FileType.SVELTE]: MimeType.TEXT_SVELTE,
|
||||
[FileType.ASTRO]: MimeType.TEXT_PLAIN,
|
||||
|
||||
// Shell/Scripting
|
||||
[FileType.SH]: MimeType.TEXT_SHELL,
|
||||
[FileType.BASH]: MimeType.TEXT_SHELL,
|
||||
[FileType.ZSH]: MimeType.TEXT_SHELL,
|
||||
[FileType.FISH]: MimeType.TEXT_SHELL,
|
||||
[FileType.BAT]: MimeType.TEXT_BATCH,
|
||||
[FileType.CMD]: MimeType.TEXT_BATCH,
|
||||
[FileType.PS1]: MimeType.TEXT_POWERSHELL,
|
||||
|
||||
// Programming languages
|
||||
[FileType.PY]: MimeType.TEXT_PYTHON,
|
||||
[FileType.RB]: MimeType.TEXT_RUBY,
|
||||
[FileType.PHP]: MimeType.TEXT_PHP,
|
||||
[FileType.JAVA]: MimeType.TEXT_JAVA,
|
||||
[FileType.C]: MimeType.TEXT_C,
|
||||
[FileType.CPP]: MimeType.TEXT_CPP,
|
||||
[FileType.CC]: MimeType.TEXT_CPP,
|
||||
[FileType.CXX]: MimeType.TEXT_CPP,
|
||||
[FileType.H]: MimeType.TEXT_C,
|
||||
[FileType.HPP]: MimeType.TEXT_CPP,
|
||||
[FileType.HXX]: MimeType.TEXT_CPP,
|
||||
[FileType.CS]: MimeType.TEXT_CSHARP,
|
||||
[FileType.GO]: MimeType.TEXT_GO,
|
||||
[FileType.RS]: MimeType.TEXT_RUST,
|
||||
[FileType.SWIFT]: MimeType.TEXT_SWIFT,
|
||||
[FileType.KT]: MimeType.TEXT_KOTLIN,
|
||||
[FileType.KTS]: MimeType.TEXT_KOTLIN,
|
||||
[FileType.SCALA]: MimeType.TEXT_SCALA,
|
||||
[FileType.M]: MimeType.TEXT_PLAIN,
|
||||
[FileType.R]: MimeType.TEXT_R,
|
||||
[FileType.R_UPPER]: MimeType.TEXT_R,
|
||||
[FileType.JL]: MimeType.TEXT_JULIA,
|
||||
[FileType.LUA]: MimeType.TEXT_LUA,
|
||||
[FileType.PL]: MimeType.TEXT_PERL,
|
||||
[FileType.PM]: MimeType.TEXT_PERL,
|
||||
[FileType.DART]: MimeType.TEXT_DART,
|
||||
|
||||
// Markup & Documentation
|
||||
[FileType.TEX]: MimeType.APPLICATION_TEX,
|
||||
[FileType.LATEX]: MimeType.APPLICATION_LATEX,
|
||||
[FileType.RST]: MimeType.TEXT_RST,
|
||||
[FileType.ADOC]: MimeType.TEXT_ASCIIDOC,
|
||||
[FileType.ASCIIDOC]: MimeType.TEXT_ASCIIDOC,
|
||||
[FileType.ORG]: MimeType.TEXT_ORG,
|
||||
[FileType.TEXTILE]: MimeType.TEXT_TEXTILE,
|
||||
[FileType.RTF]: MimeType.APPLICATION_RTF,
|
||||
|
||||
// Configuration files
|
||||
[FileType.INI]: MimeType.TEXT_PLAIN,
|
||||
[FileType.CFG]: MimeType.TEXT_PLAIN,
|
||||
[FileType.CONF]: MimeType.TEXT_PLAIN,
|
||||
[FileType.ENV]: MimeType.TEXT_PLAIN,
|
||||
[FileType.PROPERTIES]: MimeType.TEXT_PLAIN,
|
||||
[FileType.GITIGNORE]: MimeType.TEXT_PLAIN,
|
||||
[FileType.GITATTRIBUTES]: MimeType.TEXT_PLAIN,
|
||||
[FileType.EDITORCONFIG]: MimeType.TEXT_PLAIN,
|
||||
[FileType.PRETTIERRC]: MimeType.TEXT_PLAIN,
|
||||
[FileType.ESLINTRC]: MimeType.TEXT_PLAIN,
|
||||
[FileType.BABELRC]: MimeType.TEXT_PLAIN,
|
||||
[FileType.NPMRC]: MimeType.TEXT_PLAIN,
|
||||
[FileType.YARNRC]: MimeType.TEXT_PLAIN,
|
||||
[FileType.DOCKERFILE]: MimeType.APPLICATION_DOCKERFILE,
|
||||
|
||||
// Query languages
|
||||
[FileType.SQL]: MimeType.TEXT_SQL,
|
||||
[FileType.GRAPHQL]: MimeType.TEXT_GRAPHQL,
|
||||
[FileType.GQL]: MimeType.TEXT_GRAPHQL,
|
||||
|
||||
// Build & Development
|
||||
[FileType.MAKEFILE]: MimeType.APPLICATION_MAKEFILE,
|
||||
[FileType.MK]: MimeType.APPLICATION_MAKEFILE,
|
||||
[FileType.GRADLE]: MimeType.APPLICATION_GRADLE,
|
||||
[FileType.DIFF]: MimeType.TEXT_PLAIN,
|
||||
[FileType.PATCH]: MimeType.TEXT_PLAIN,
|
||||
|
||||
// Logs
|
||||
[FileType.LOG]: MimeType.TEXT_PLAIN,
|
||||
|
||||
[FileType.UNKNOWN]: MimeType.UNKNOWN
|
||||
};
|
||||
|
||||
export const MimeTypeToFileTypes: Record<MimeType, FileType[]> = {
|
||||
// Text
|
||||
[MimeType.TEXT_PLAIN]: [FileType.BASE, FileType.TXT, FileType.TEXT, FileType.INI, FileType.CFG, FileType.CONF, FileType.ENV, FileType.PROPERTIES, FileType.LOG, FileType.GITIGNORE, FileType.GITATTRIBUTES, FileType.EDITORCONFIG, FileType.PRETTIERRC, FileType.ESLINTRC, FileType.BABELRC, FileType.NPMRC, FileType.YARNRC, FileType.DIFF, FileType.PATCH],
|
||||
[MimeType.TEXT_MARKDOWN]: [FileType.MD],
|
||||
[MimeType.TEXT_MD]: [FileType.MD],
|
||||
[MimeType.TEXT_HTML]: [FileType.HTML],
|
||||
[MimeType.TEXT_CSS]: [FileType.CSS],
|
||||
[MimeType.TEXT_CSV]: [FileType.CSV],
|
||||
[MimeType.TEXT_TSV]: [FileType.TSV],
|
||||
[MimeType.TEXT_JAVASCRIPT]: [FileType.JS, FileType.MJS, FileType.CJS],
|
||||
[MimeType.TEXT_TYPESCRIPT]: [FileType.TS],
|
||||
[MimeType.TEXT_JSX]: [FileType.JSX],
|
||||
[MimeType.TEXT_TSX]: [FileType.TSX],
|
||||
[MimeType.TEXT_SASS]: [FileType.SASS],
|
||||
[MimeType.TEXT_SCSS]: [FileType.SCSS],
|
||||
[MimeType.TEXT_VUE]: [FileType.VUE],
|
||||
[MimeType.TEXT_SVELTE]: [FileType.SVELTE],
|
||||
[MimeType.TEXT_PYTHON]: [FileType.PY],
|
||||
[MimeType.TEXT_RUBY]: [FileType.RB],
|
||||
[MimeType.TEXT_PHP]: [FileType.PHP],
|
||||
[MimeType.TEXT_JAVA]: [FileType.JAVA],
|
||||
[MimeType.TEXT_JAVA_SOURCE]: [FileType.JAVA],
|
||||
[MimeType.TEXT_C]: [FileType.C, FileType.H],
|
||||
[MimeType.TEXT_CSRC]: [FileType.C],
|
||||
[MimeType.TEXT_CHDR]: [FileType.H],
|
||||
[MimeType.TEXT_CPP]: [FileType.CPP, FileType.CC, FileType.CXX, FileType.HPP, FileType.HXX],
|
||||
[MimeType.TEXT_CPPSRC]: [FileType.CPP, FileType.CC, FileType.CXX],
|
||||
[MimeType.TEXT_CPPHDR]: [FileType.HPP, FileType.HXX],
|
||||
[MimeType.TEXT_CSHARP]: [FileType.CS],
|
||||
[MimeType.TEXT_GO]: [FileType.GO],
|
||||
[MimeType.TEXT_RUST]: [FileType.RS],
|
||||
[MimeType.TEXT_SWIFT]: [FileType.SWIFT],
|
||||
[MimeType.TEXT_KOTLIN]: [FileType.KT, FileType.KTS],
|
||||
[MimeType.TEXT_SCALA]: [FileType.SCALA],
|
||||
[MimeType.TEXT_R]: [FileType.R, FileType.R_UPPER],
|
||||
[MimeType.TEXT_JULIA]: [FileType.JL],
|
||||
[MimeType.TEXT_LUA]: [FileType.LUA],
|
||||
[MimeType.TEXT_PERL]: [FileType.PL, FileType.PM],
|
||||
[MimeType.TEXT_DART]: [FileType.DART],
|
||||
[MimeType.TEXT_SHELL]: [FileType.SH, FileType.BASH, FileType.ZSH, FileType.FISH],
|
||||
[MimeType.TEXT_SH]: [FileType.SH, FileType.BASH, FileType.ZSH, FileType.FISH],
|
||||
[MimeType.TEXT_BATCH]: [FileType.BAT, FileType.CMD],
|
||||
[MimeType.TEXT_POWERSHELL]: [FileType.PS1],
|
||||
[MimeType.TEXT_SQL]: [FileType.SQL],
|
||||
[MimeType.TEXT_GRAPHQL]: [FileType.GRAPHQL, FileType.GQL],
|
||||
[MimeType.TEXT_XML]: [FileType.XML],
|
||||
[MimeType.TEXT_YAML]: [FileType.YAML, FileType.YML],
|
||||
|
||||
// Application
|
||||
[MimeType.APPLICATION_JSON]: [FileType.CANVAS, FileType.JSON],
|
||||
[MimeType.APPLICATION_XML]: [FileType.XML],
|
||||
[MimeType.APPLICATION_PDF]: [FileType.PDF],
|
||||
[MimeType.APPLICATION_RTF]: [FileType.RTF],
|
||||
[MimeType.APPLICATION_YAML]: [FileType.YAML, FileType.YML],
|
||||
[MimeType.APPLICATION_TOML]: [FileType.TOML],
|
||||
[MimeType.APPLICATION_TEX]: [FileType.TEX],
|
||||
[MimeType.APPLICATION_LATEX]: [FileType.LATEX],
|
||||
[MimeType.APPLICATION_MAKEFILE]: [FileType.MAKEFILE, FileType.MK],
|
||||
[MimeType.APPLICATION_GRADLE]: [FileType.GRADLE],
|
||||
[MimeType.APPLICATION_DOCKERFILE]: [FileType.DOCKERFILE],
|
||||
[MimeType.APPLICATION_PYTHON_CODE]: [FileType.PY],
|
||||
[MimeType.APPLICATION_JAVASCRIPT]: [FileType.JS, FileType.MJS, FileType.CJS],
|
||||
[MimeType.APPLICATION_TYPESCRIPT]: [FileType.TS],
|
||||
[MimeType.APPLICATION_SH]: [FileType.SH, FileType.BASH, FileType.ZSH, FileType.FISH],
|
||||
|
||||
// Markup formats
|
||||
[MimeType.TEXT_RST]: [FileType.RST],
|
||||
[MimeType.TEXT_ASCIIDOC]: [FileType.ADOC, FileType.ASCIIDOC],
|
||||
[MimeType.TEXT_ORG]: [FileType.ORG],
|
||||
[MimeType.TEXT_TEXTILE]: [FileType.TEXTILE],
|
||||
|
||||
// Images
|
||||
[MimeType.IMAGE_AVIF]: [FileType.AVIF],
|
||||
[MimeType.IMAGE_BMP]: [FileType.BMP],
|
||||
[MimeType.IMAGE_GIF]: [FileType.GIF],
|
||||
[MimeType.IMAGE_JPEG]: [FileType.JPEG, FileType.JPG],
|
||||
[MimeType.IMAGE_PNG]: [FileType.PNG],
|
||||
[MimeType.IMAGE_SVG]: [FileType.SVG],
|
||||
[MimeType.IMAGE_WEBP]: [FileType.WEBP],
|
||||
|
||||
// Audio
|
||||
[MimeType.AUDIO_FLAC]: [FileType.FLAC],
|
||||
[MimeType.AUDIO_MP4]: [FileType.M4A],
|
||||
[MimeType.AUDIO_MPEG]: [FileType.MP3],
|
||||
[MimeType.AUDIO_OGG]: [FileType.OGG],
|
||||
[MimeType.AUDIO_WAV]: [FileType.WAV],
|
||||
[MimeType.AUDIO_WEBM]: [FileType.WEBM_AUDIO],
|
||||
|
||||
// Video
|
||||
[MimeType.VIDEO_3GPP]: [FileType.THREE_GP],
|
||||
[MimeType.VIDEO_MATROSKA]: [FileType.MKV],
|
||||
[MimeType.VIDEO_QUICKTIME]: [FileType.MOV],
|
||||
[MimeType.VIDEO_MP4]: [FileType.MP4],
|
||||
[MimeType.VIDEO_OGG]: [FileType.OGV],
|
||||
[MimeType.VIDEO_WEBM]: [FileType.WEBM, FileType.WEBM_VIDEO],
|
||||
|
||||
[MimeType.UNKNOWN]: [FileType.UNKNOWN]
|
||||
};
|
||||
|
|
@ -1,5 +1,3 @@
|
|||
import { FileType } from "./FileType";
|
||||
|
||||
export enum MimeType {
|
||||
// Text
|
||||
TEXT_PLAIN = "text/plain",
|
||||
|
|
@ -109,151 +107,3 @@ export function toMimeType(mimeType: string): MimeType {
|
|||
export function isKnownMimeType(value: string): value is MimeType {
|
||||
return Object.values(MimeType).includes(value as MimeType) && value !== MimeType.UNKNOWN.toString();
|
||||
}
|
||||
|
||||
export const FileTypeToMimeType: Record<FileType, MimeType> = {
|
||||
// ----- Types officially supported by Obsidian -----
|
||||
[FileType.MD]: MimeType.TEXT_MARKDOWN,
|
||||
[FileType.BASE]: MimeType.TEXT_PLAIN,
|
||||
[FileType.CANVAS]: MimeType.APPLICATION_JSON,
|
||||
|
||||
// images
|
||||
[FileType.AVIF]: MimeType.IMAGE_AVIF,
|
||||
[FileType.BMP]: MimeType.IMAGE_BMP,
|
||||
[FileType.GIF]: MimeType.IMAGE_GIF,
|
||||
[FileType.JPEG]: MimeType.IMAGE_JPEG,
|
||||
[FileType.JPG]: MimeType.IMAGE_JPEG,
|
||||
[FileType.PNG]: MimeType.IMAGE_PNG,
|
||||
[FileType.SVG]: MimeType.IMAGE_SVG,
|
||||
[FileType.WEBP]: MimeType.IMAGE_WEBP,
|
||||
|
||||
// Note: WEBM is used for both audio and video, defaulting to video
|
||||
[FileType.WEBM]: MimeType.VIDEO_WEBM,
|
||||
|
||||
// audio
|
||||
[FileType.FLAC]: MimeType.AUDIO_FLAC,
|
||||
[FileType.M4A]: MimeType.AUDIO_MP4,
|
||||
[FileType.MP3]: MimeType.AUDIO_MPEG,
|
||||
[FileType.OGG]: MimeType.AUDIO_OGG,
|
||||
[FileType.WAV]: MimeType.AUDIO_WAV,
|
||||
[FileType.THREE_GP]: MimeType.VIDEO_3GPP,
|
||||
|
||||
// video
|
||||
[FileType.MKV]: MimeType.VIDEO_MATROSKA,
|
||||
[FileType.MOV]: MimeType.VIDEO_QUICKTIME,
|
||||
[FileType.MP4]: MimeType.VIDEO_MP4,
|
||||
[FileType.OGV]: MimeType.VIDEO_OGG,
|
||||
|
||||
// pdf
|
||||
[FileType.PDF]: MimeType.APPLICATION_PDF,
|
||||
|
||||
// ----- Types not officially supported by Obsidian -----
|
||||
// Plain text
|
||||
[FileType.TXT]: MimeType.TEXT_PLAIN,
|
||||
[FileType.TEXT]: MimeType.TEXT_PLAIN,
|
||||
|
||||
// Data formats
|
||||
[FileType.JSON]: MimeType.APPLICATION_JSON,
|
||||
[FileType.XML]: MimeType.APPLICATION_XML,
|
||||
[FileType.CSV]: MimeType.TEXT_CSV,
|
||||
[FileType.TSV]: MimeType.TEXT_TSV,
|
||||
[FileType.YAML]: MimeType.APPLICATION_YAML,
|
||||
[FileType.YML]: MimeType.APPLICATION_YAML,
|
||||
[FileType.TOML]: MimeType.APPLICATION_TOML,
|
||||
|
||||
// Web languages
|
||||
[FileType.HTML]: MimeType.TEXT_HTML,
|
||||
[FileType.CSS]: MimeType.TEXT_CSS,
|
||||
[FileType.SASS]: MimeType.TEXT_SASS,
|
||||
[FileType.SCSS]: MimeType.TEXT_SCSS,
|
||||
|
||||
// JavaScript/TypeScript ecosystem
|
||||
[FileType.JS]: MimeType.TEXT_JAVASCRIPT,
|
||||
[FileType.MJS]: MimeType.TEXT_JAVASCRIPT,
|
||||
[FileType.CJS]: MimeType.TEXT_JAVASCRIPT,
|
||||
[FileType.TS]: MimeType.TEXT_TYPESCRIPT,
|
||||
[FileType.JSX]: MimeType.TEXT_JSX,
|
||||
[FileType.TSX]: MimeType.TEXT_TSX,
|
||||
[FileType.VUE]: MimeType.TEXT_VUE,
|
||||
[FileType.SVELTE]: MimeType.TEXT_SVELTE,
|
||||
[FileType.ASTRO]: MimeType.TEXT_PLAIN,
|
||||
|
||||
// Shell/Scripting
|
||||
[FileType.SH]: MimeType.TEXT_SHELL,
|
||||
[FileType.BASH]: MimeType.TEXT_SHELL,
|
||||
[FileType.ZSH]: MimeType.TEXT_SHELL,
|
||||
[FileType.FISH]: MimeType.TEXT_SHELL,
|
||||
[FileType.BAT]: MimeType.TEXT_BATCH,
|
||||
[FileType.CMD]: MimeType.TEXT_BATCH,
|
||||
[FileType.PS1]: MimeType.TEXT_POWERSHELL,
|
||||
|
||||
// Programming languages
|
||||
[FileType.PY]: MimeType.TEXT_PYTHON,
|
||||
[FileType.RB]: MimeType.TEXT_RUBY,
|
||||
[FileType.PHP]: MimeType.TEXT_PHP,
|
||||
[FileType.JAVA]: MimeType.TEXT_JAVA,
|
||||
[FileType.C]: MimeType.TEXT_C,
|
||||
[FileType.CPP]: MimeType.TEXT_CPP,
|
||||
[FileType.CC]: MimeType.TEXT_CPP,
|
||||
[FileType.CXX]: MimeType.TEXT_CPP,
|
||||
[FileType.H]: MimeType.TEXT_C,
|
||||
[FileType.HPP]: MimeType.TEXT_CPP,
|
||||
[FileType.HXX]: MimeType.TEXT_CPP,
|
||||
[FileType.CS]: MimeType.TEXT_CSHARP,
|
||||
[FileType.GO]: MimeType.TEXT_GO,
|
||||
[FileType.RS]: MimeType.TEXT_RUST,
|
||||
[FileType.SWIFT]: MimeType.TEXT_SWIFT,
|
||||
[FileType.KT]: MimeType.TEXT_KOTLIN,
|
||||
[FileType.KTS]: MimeType.TEXT_KOTLIN,
|
||||
[FileType.SCALA]: MimeType.TEXT_SCALA,
|
||||
[FileType.M]: MimeType.TEXT_PLAIN,
|
||||
[FileType.R]: MimeType.TEXT_R,
|
||||
[FileType.R_UPPER]: MimeType.TEXT_R,
|
||||
[FileType.JL]: MimeType.TEXT_JULIA,
|
||||
[FileType.LUA]: MimeType.TEXT_LUA,
|
||||
[FileType.PL]: MimeType.TEXT_PERL,
|
||||
[FileType.PM]: MimeType.TEXT_PERL,
|
||||
[FileType.DART]: MimeType.TEXT_DART,
|
||||
|
||||
// Markup & Documentation
|
||||
[FileType.TEX]: MimeType.APPLICATION_TEX,
|
||||
[FileType.LATEX]: MimeType.APPLICATION_LATEX,
|
||||
[FileType.RST]: MimeType.TEXT_RST,
|
||||
[FileType.ADOC]: MimeType.TEXT_ASCIIDOC,
|
||||
[FileType.ASCIIDOC]: MimeType.TEXT_ASCIIDOC,
|
||||
[FileType.ORG]: MimeType.TEXT_ORG,
|
||||
[FileType.TEXTILE]: MimeType.TEXT_TEXTILE,
|
||||
[FileType.RTF]: MimeType.APPLICATION_RTF,
|
||||
|
||||
// Configuration files
|
||||
[FileType.INI]: MimeType.TEXT_PLAIN,
|
||||
[FileType.CFG]: MimeType.TEXT_PLAIN,
|
||||
[FileType.CONF]: MimeType.TEXT_PLAIN,
|
||||
[FileType.ENV]: MimeType.TEXT_PLAIN,
|
||||
[FileType.PROPERTIES]: MimeType.TEXT_PLAIN,
|
||||
[FileType.GITIGNORE]: MimeType.TEXT_PLAIN,
|
||||
[FileType.GITATTRIBUTES]: MimeType.TEXT_PLAIN,
|
||||
[FileType.EDITORCONFIG]: MimeType.TEXT_PLAIN,
|
||||
[FileType.PRETTIERRC]: MimeType.TEXT_PLAIN,
|
||||
[FileType.ESLINTRC]: MimeType.TEXT_PLAIN,
|
||||
[FileType.BABELRC]: MimeType.TEXT_PLAIN,
|
||||
[FileType.NPMRC]: MimeType.TEXT_PLAIN,
|
||||
[FileType.YARNRC]: MimeType.TEXT_PLAIN,
|
||||
[FileType.DOCKERFILE]: MimeType.APPLICATION_DOCKERFILE,
|
||||
|
||||
// Query languages
|
||||
[FileType.SQL]: MimeType.TEXT_SQL,
|
||||
[FileType.GRAPHQL]: MimeType.TEXT_GRAPHQL,
|
||||
[FileType.GQL]: MimeType.TEXT_GRAPHQL,
|
||||
|
||||
// Build & Development
|
||||
[FileType.MAKEFILE]: MimeType.APPLICATION_MAKEFILE,
|
||||
[FileType.MK]: MimeType.APPLICATION_MAKEFILE,
|
||||
[FileType.GRADLE]: MimeType.APPLICATION_GRADLE,
|
||||
[FileType.DIFF]: MimeType.TEXT_PLAIN,
|
||||
[FileType.PATCH]: MimeType.TEXT_PLAIN,
|
||||
|
||||
// Logs
|
||||
[FileType.LOG]: MimeType.TEXT_PLAIN,
|
||||
|
||||
[FileType.UNKNOWN]: MimeType.UNKNOWN
|
||||
};
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import { AbortService } from "./AbortService";
|
|||
import { Exception } from "Helpers/Exception";
|
||||
import { Copy } from "Enums/Copy";
|
||||
import type { Attachment } from "Conversations/Attachment";
|
||||
import { Reference } from "Conversations/Reference";
|
||||
|
||||
export interface IChatServiceCallbacks {
|
||||
onSubmit: () => void;
|
||||
|
|
@ -68,11 +69,12 @@ export class ChatService {
|
|||
await this.abortService.abortableOperation(async () => {
|
||||
const firstMessage = conversation.contents.length === 0;
|
||||
|
||||
conversation.contents.push(new ConversationContent({
|
||||
const conversationContent = new ConversationContent({
|
||||
role: Role.User,
|
||||
content: formattedRequest,
|
||||
displayContent: userRequest
|
||||
}));
|
||||
});
|
||||
conversation.contents.push(conversationContent);
|
||||
await this.saveConversation(conversation);
|
||||
|
||||
if (attachments.length > 0) {
|
||||
|
|
@ -82,6 +84,9 @@ export class ChatService {
|
|||
attachments: attachments,
|
||||
shouldDisplayContent: false
|
||||
}));
|
||||
|
||||
conversationContent.references = attachments.map(attachment =>
|
||||
new Reference(attachment.fileName, attachment.approximateFileSizeMB()));
|
||||
}
|
||||
|
||||
callbacks.onSubmit();
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { ConversationContent } from "Conversations/ConversationContent";
|
|||
import { Attachment } from "Conversations/Attachment";
|
||||
import { Exception } from "Helpers/Exception";
|
||||
import type { IAIFileService } from "AIClasses/IAIFileService";
|
||||
import { Reference } from "Conversations/Reference";
|
||||
|
||||
export class ConversationFileSystemService {
|
||||
|
||||
|
|
@ -54,6 +55,7 @@ export class ConversationFileSystemService {
|
|||
functionCall: content.functionCall,
|
||||
functionResponse: content.functionResponse,
|
||||
attachments: content.attachments,
|
||||
references: content.references,
|
||||
shouldDisplayContent: content.shouldDisplayContent,
|
||||
toolId: content.toolId,
|
||||
thoughtSignature: content.thoughtSignature,
|
||||
|
|
@ -150,6 +152,7 @@ export class ConversationFileSystemService {
|
|||
conversation.contents = result.contents.map(content => {
|
||||
// Reconstruct Attachment instances from plain objects
|
||||
const attachments = this.deserializeAttachments(content.attachments);
|
||||
const references = this.deserializeReferences(content.references);
|
||||
|
||||
return new ConversationContent({
|
||||
role: content.role,
|
||||
|
|
@ -159,6 +162,7 @@ export class ConversationFileSystemService {
|
|||
functionCall: content.functionCall,
|
||||
functionResponse: content.functionResponse,
|
||||
attachments: attachments,
|
||||
references: references,
|
||||
shouldDisplayContent: content.shouldDisplayContent,
|
||||
toolId: content.toolId,
|
||||
thoughtSignature: content.thoughtSignature,
|
||||
|
|
@ -185,6 +189,19 @@ export class ConversationFileSystemService {
|
|||
));
|
||||
}
|
||||
|
||||
private deserializeReferences(referencesData: unknown): Reference[] {
|
||||
if (!Array.isArray(referencesData)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return referencesData
|
||||
.filter(Reference.isReferenceData)
|
||||
.map(referenceData => new Reference(
|
||||
referenceData.fileName,
|
||||
referenceData.size
|
||||
));
|
||||
}
|
||||
|
||||
private async attemptAIFileDeletion(conversation: Conversation) {
|
||||
try {
|
||||
await this.aiFileService?.refreshCache();
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { Exception } from "Helpers/Exception";
|
|||
import { isSearchTriggerElement } from "../Enums/SearchTrigger";
|
||||
import { Attachment } from "Conversations/Attachment";
|
||||
import { arrayBufferToBase64 } from "obsidian";
|
||||
import { FileTypeToMimeType } from "Enums/MimeType";
|
||||
import { FileTypeToMimeType } from "Enums/FileTypeMimeTypeMapping";
|
||||
import * as path from "path-browserify";
|
||||
import { pathExtname } from "Helpers/Helpers";
|
||||
import { FileType, toFileType } from "Enums/FileType";
|
||||
|
|
@ -45,8 +45,10 @@ export class InputService {
|
|||
}
|
||||
}
|
||||
|
||||
// get text as sometimes uri's come through as plain text
|
||||
const textUriList = this.getTextFromDataTransfer(dataTransfer);
|
||||
const uriList = dataTransfer.getData("text/uri-list");
|
||||
const uris = uriList.split("\n").map(uri => uri.trim())
|
||||
const uris = `${uriList}\n${textUriList}`.split("\n").unique().map(uri => uri.trim())
|
||||
.filter(uri => uri.length > 0 && !uri.startsWith("#"));
|
||||
|
||||
for (const uri of uris) {
|
||||
|
|
|
|||
|
|
@ -61,6 +61,17 @@
|
|||
/* CSS Variables for Common Components */
|
||||
/* ============================== */
|
||||
|
||||
.fading-stream {
|
||||
opacity: 0;
|
||||
animation: fadeIn 1s forwards;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.transparent-button {
|
||||
border: none !important;
|
||||
outline: none !important;
|
||||
|
|
|
|||
|
|
@ -1026,7 +1026,7 @@ describe('Claude', () => {
|
|||
expect(parsed).toHaveLength(1);
|
||||
expect(parsed[0]).toEqual({
|
||||
type: 'text',
|
||||
text: 'Unsupported image format: photo.bmp'
|
||||
text: 'Unsupported mime type \'image/bmp\': photo.bmp'
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -1132,7 +1132,7 @@ describe('Claude', () => {
|
|||
expect(parsed[1].type).toBe('image');
|
||||
expect(parsed[2]).toEqual({
|
||||
type: 'text',
|
||||
text: 'Unsupported image format: bad.bmp'
|
||||
text: 'Unsupported mime type \'image/bmp\': bad.bmp'
|
||||
});
|
||||
expect(parsed[3].type).toBe('text');
|
||||
expect(parsed[3].text).toBe('doc.pdf');
|
||||
|
|
|
|||
|
|
@ -1161,7 +1161,7 @@ describe('Gemini', () => {
|
|||
|
||||
expect(parsed).toHaveLength(1);
|
||||
expect(parsed[0]).toEqual({
|
||||
text: 'Unsupported image format: animation.gif'
|
||||
text: 'Unsupported mime type \'image/gif\': animation.gif'
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -1180,7 +1180,7 @@ describe('Gemini', () => {
|
|||
|
||||
expect(parsed).toHaveLength(1);
|
||||
expect(parsed[0]).toEqual({
|
||||
text: 'Unsupported image format: photo.bmp'
|
||||
text: 'Unsupported mime type \'image/bmp\': photo.bmp'
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -1281,7 +1281,7 @@ describe('Gemini', () => {
|
|||
expect(parsed[0]).toEqual({ text: 'good.jpg' });
|
||||
expect(parsed[1]).toHaveProperty('fileData');
|
||||
expect(parsed[2]).toEqual({
|
||||
text: 'Unsupported image format: bad.bmp'
|
||||
text: 'Unsupported mime type \'image/bmp\': bad.bmp'
|
||||
});
|
||||
expect(parsed[3]).toEqual({ text: 'doc.pdf' });
|
||||
expect(parsed[4]).toHaveProperty('fileData');
|
||||
|
|
|
|||
|
|
@ -1061,7 +1061,7 @@ describe('OpenAI', () => {
|
|||
expect(parsed[0].content).toHaveLength(1);
|
||||
expect(parsed[0].content[0]).toEqual({
|
||||
type: 'input_text',
|
||||
text: 'Unsupported image format: photo.gif'
|
||||
text: 'Unsupported mime type \'image/gif\': photo.gif'
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -1171,7 +1171,7 @@ describe('OpenAI', () => {
|
|||
expect(parsed[0].content[0].type).toBe('input_image');
|
||||
expect(parsed[0].content[1]).toEqual({
|
||||
type: 'input_text',
|
||||
text: 'Unsupported image format: bad.bmp'
|
||||
text: 'Unsupported mime type \'image/bmp\': bad.bmp'
|
||||
});
|
||||
expect(parsed[0].content[2].type).toBe('input_file');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
|
||||
import { InputService } from '../../Services/InputService';
|
||||
import { RegisterSingleton, DeregisterAllServices } from '../../Services/DependencyService';
|
||||
import { Services } from '../../Services/Services';
|
||||
|
||||
/**
|
||||
* UNIT TESTS
|
||||
|
|
@ -10,11 +12,28 @@ import { InputService } from '../../Services/InputService';
|
|||
|
||||
describe('InputService', () => {
|
||||
let service: InputService;
|
||||
let mockFileSystemService: any;
|
||||
|
||||
beforeEach(() => {
|
||||
// Setup minimal mock for FileSystemService
|
||||
mockFileSystemService = {
|
||||
getFile: vi.fn(),
|
||||
readFile: vi.fn(),
|
||||
fileExists: vi.fn()
|
||||
};
|
||||
|
||||
// Register dependency
|
||||
RegisterSingleton(Services.FileSystemService, mockFileSystemService);
|
||||
|
||||
service = new InputService();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// Clear singleton registry to prevent memory leaks
|
||||
DeregisterAllServices();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe('isPrintableKey', () => {
|
||||
it('should return true for single character keys', () => {
|
||||
expect(service.isPrintableKey('a')).toBe(true);
|
||||
|
|
@ -132,7 +151,7 @@ describe('InputService', () => {
|
|||
});
|
||||
|
||||
|
||||
describe('getPlainTextFromClipboard', () => {
|
||||
describe('getTextFromDataTransfer', () => {
|
||||
it('should extract text/plain from DataTransfer', () => {
|
||||
const mockClipboardData = {
|
||||
getData: vi.fn((type: string) => {
|
||||
|
|
@ -141,13 +160,13 @@ describe('InputService', () => {
|
|||
})
|
||||
} as unknown as DataTransfer;
|
||||
|
||||
const result = service.getPlainTextFromClipboard(mockClipboardData);
|
||||
const result = service.getTextFromDataTransfer(mockClipboardData);
|
||||
expect(result).toBe('Plain text content');
|
||||
expect(mockClipboardData.getData).toHaveBeenCalledWith('text/plain');
|
||||
});
|
||||
|
||||
it('should return empty string for null clipboard', () => {
|
||||
const result = service.getPlainTextFromClipboard(null);
|
||||
const result = service.getTextFromDataTransfer(null);
|
||||
expect(result).toBe('');
|
||||
});
|
||||
|
||||
|
|
@ -156,7 +175,7 @@ describe('InputService', () => {
|
|||
getData: vi.fn(() => '')
|
||||
} as unknown as DataTransfer;
|
||||
|
||||
const result = service.getPlainTextFromClipboard(mockClipboardData);
|
||||
const result = service.getTextFromDataTransfer(mockClipboardData);
|
||||
expect(result).toBe('');
|
||||
});
|
||||
|
||||
|
|
@ -165,7 +184,7 @@ describe('InputService', () => {
|
|||
getData: vi.fn(() => null)
|
||||
} as unknown as DataTransfer;
|
||||
|
||||
const result = service.getPlainTextFromClipboard(mockClipboardData);
|
||||
const result = service.getTextFromDataTransfer(mockClipboardData);
|
||||
expect(result).toBe('');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue