mirror of
https://github.com/DustinBrett/daedalOS.git
synced 2025-12-06 12:20:20 +01:00
This commit is contained in:
parent
53c1e1bfbf
commit
f941b6a65d
|
|
@ -36,6 +36,7 @@ import {
|
|||
SHORTCUT_EXTENSION,
|
||||
SPREADSHEET_FORMATS,
|
||||
TEXT_EDITORS,
|
||||
TEXT_FILE_EXTENSIONS,
|
||||
VIDEO_FILE_EXTENSIONS,
|
||||
} from "utils/constants";
|
||||
import {
|
||||
|
|
@ -52,6 +53,12 @@ import {
|
|||
} from "utils/imagemagick/formats";
|
||||
import { type ImageMagickConvertFile } from "utils/imagemagick/types";
|
||||
import { Share } from "components/system/Menu/MenuIcons";
|
||||
import { useWindowAI } from "hooks/useWindowAI";
|
||||
import { getNavButtonByTitle } from "hooks/useGlobalKeyboardShortcuts";
|
||||
import {
|
||||
AI_DISPLAY_TITLE,
|
||||
AI_STAGE,
|
||||
} from "components/system/Taskbar/AI/constants";
|
||||
|
||||
const { alias } = PACKAGE_DATA;
|
||||
|
||||
|
|
@ -92,6 +99,7 @@ const useFileContextMenu = (
|
|||
updateFolder,
|
||||
} = useFileSystem();
|
||||
const { contextMenu } = useMenu();
|
||||
const hasWindowAI = useWindowAI();
|
||||
const { onContextMenuCapture, ...contextMenuHandlers } = useMemo(
|
||||
() =>
|
||||
contextMenu?.(() => {
|
||||
|
|
@ -485,6 +493,35 @@ const useFileContextMenu = (
|
|||
});
|
||||
}
|
||||
|
||||
if (
|
||||
hasWindowAI &&
|
||||
"summarizer" in window.ai &&
|
||||
TEXT_FILE_EXTENSIONS.has(urlExtension)
|
||||
) {
|
||||
const aiCommand = (command: string): void => {
|
||||
const aiButton = getNavButtonByTitle(AI_DISPLAY_TITLE);
|
||||
|
||||
if (aiButton) {
|
||||
window.initialAiPrompt = `${command}: ${url}`;
|
||||
aiButton.click();
|
||||
}
|
||||
};
|
||||
|
||||
menuItems.unshift(MENU_SEPERATOR, {
|
||||
label: `AI (${AI_STAGE})`,
|
||||
menu: [
|
||||
...("summarizer" in window.ai
|
||||
? [
|
||||
{
|
||||
action: () => aiCommand("Summarize"),
|
||||
label: "Summarize Text",
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
hasBackgroundVideoExtension ||
|
||||
(IMAGE_FILE_EXTENSIONS.has(pathExtension) &&
|
||||
|
|
@ -610,6 +647,7 @@ const useFileContextMenu = (
|
|||
extractFiles,
|
||||
fileManagerId,
|
||||
focusedEntries,
|
||||
hasWindowAI,
|
||||
isFocusedEntry,
|
||||
lstat,
|
||||
mapFs,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { extname } from "path";
|
||||
import { useTheme } from "styled-components";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import {
|
||||
|
|
@ -18,7 +19,7 @@ import {
|
|||
} from "components/system/Taskbar/AI/icons";
|
||||
import useAITransition from "components/system/Taskbar/AI/useAITransition";
|
||||
import {
|
||||
AI_STAGE,
|
||||
AI_DISPLAY_TITLE,
|
||||
AI_TITLE,
|
||||
AI_WORKER,
|
||||
DEFAULT_CONVO_STYLE,
|
||||
|
|
@ -41,6 +42,7 @@ import useWorker from "hooks/useWorker";
|
|||
import useFocusable from "components/system/Window/useFocusable";
|
||||
import { useSession } from "contexts/session";
|
||||
import { useWindowAI } from "hooks/useWindowAI";
|
||||
import { useFileSystem } from "contexts/fileSystem";
|
||||
|
||||
type AIChatProps = {
|
||||
toggleAI: () => void;
|
||||
|
|
@ -62,7 +64,7 @@ const AIChat: FC<AIChatProps> = ({ toggleAI }) => {
|
|||
const [convoStyle, setConvoStyle] = useState(DEFAULT_CONVO_STYLE);
|
||||
const [primaryColor, secondaryColor, tertiaryColor] =
|
||||
taskbarColor.ai[convoStyle];
|
||||
const [promptText, setPromptText] = useState("");
|
||||
const [promptText, setPromptText] = useState(window.initialAiPrompt || "");
|
||||
const textAreaRef = useRef<HTMLTextAreaElement>(null);
|
||||
const sectionRef = useRef<HTMLDivElement>(null);
|
||||
const typing = promptText.length > 0;
|
||||
|
|
@ -155,6 +157,45 @@ const AIChat: FC<AIChatProps> = ({ toggleAI }) => {
|
|||
textArea.style.height = "auto";
|
||||
textArea.style.height = `${textArea.scrollHeight}px`;
|
||||
}, []);
|
||||
const { exists, readFile, stat } = useFileSystem();
|
||||
const sendMessage = useCallback(async () => {
|
||||
const { text } = conversation[conversation.length - 1];
|
||||
|
||||
setResponding(true);
|
||||
|
||||
sessionIdRef.current ||= Date.now();
|
||||
|
||||
let summarizeText = "";
|
||||
const lcText = text.toLowerCase();
|
||||
|
||||
if (lcText.startsWith("summarize: /")) {
|
||||
const docPath = text.slice(11).trim();
|
||||
|
||||
if ((await exists(docPath)) && !(await stat(docPath)).isDirectory()) {
|
||||
let docText = (await readFile(docPath)).toString();
|
||||
|
||||
if ([".html", ".htm", ".whtml"].includes(extname(docPath))) {
|
||||
const domContent = new DOMParser().parseFromString(
|
||||
docText,
|
||||
"text/html"
|
||||
);
|
||||
|
||||
docText = domContent.body.textContent || "";
|
||||
}
|
||||
|
||||
summarizeText = docText;
|
||||
}
|
||||
}
|
||||
|
||||
aiWorker.current?.postMessage({
|
||||
hasWindowAI,
|
||||
id: sessionIdRef.current,
|
||||
streamId: STREAMING_SUPPORT ? conversation.length : undefined,
|
||||
style: convoStyle,
|
||||
summarizeText,
|
||||
text,
|
||||
});
|
||||
}, [aiWorker, conversation, convoStyle, exists, hasWindowAI, readFile, stat]);
|
||||
|
||||
useEffect(() => {
|
||||
textAreaRef.current?.focus(PREVENT_SCROLL);
|
||||
|
|
@ -192,21 +233,16 @@ const AIChat: FC<AIChatProps> = ({ toggleAI }) => {
|
|||
conversation.length > 0 &&
|
||||
conversation[conversation.length - 1].type === "user"
|
||||
) {
|
||||
const { text } = conversation[conversation.length - 1];
|
||||
|
||||
setResponding(true);
|
||||
|
||||
sessionIdRef.current ||= Date.now();
|
||||
|
||||
aiWorker.current.postMessage({
|
||||
hasWindowAI,
|
||||
id: sessionIdRef.current,
|
||||
streamId: STREAMING_SUPPORT ? conversation.length : undefined,
|
||||
style: convoStyle,
|
||||
text,
|
||||
});
|
||||
sendMessage();
|
||||
}
|
||||
}, [aiWorker, conversation, convoStyle, hasWindowAI]);
|
||||
}, [aiWorker, conversation, sendMessage]);
|
||||
|
||||
useEffect(() => {
|
||||
if (window.initialAiPrompt && aiWorker.current) {
|
||||
window.initialAiPrompt = "";
|
||||
addUserPrompt();
|
||||
}
|
||||
}, [addUserPrompt, aiWorker]);
|
||||
|
||||
useEffect(() => {
|
||||
const workerRef = aiWorker.current;
|
||||
|
|
@ -259,7 +295,7 @@ const AIChat: FC<AIChatProps> = ({ toggleAI }) => {
|
|||
>
|
||||
<div className="header">
|
||||
<header>
|
||||
{`${AI_TITLE} (${AI_STAGE})`}
|
||||
{AI_DISPLAY_TITLE}
|
||||
<nav>
|
||||
<Button
|
||||
className="close"
|
||||
|
|
|
|||
|
|
@ -49,6 +49,9 @@ let responding = false;
|
|||
|
||||
let sessionId = 0;
|
||||
let session: AILanguageModel | ChatCompletionMessageParam[] | undefined;
|
||||
let summarizer: AISummarizer | undefined;
|
||||
let prompts: (AILanguageModelAssistantPrompt | AILanguageModelUserPrompt)[] =
|
||||
[];
|
||||
let engine: MLCEngine;
|
||||
|
||||
let markedLoaded = false;
|
||||
|
|
@ -67,6 +70,8 @@ globalThis.addEventListener(
|
|||
sessionId = data.id;
|
||||
|
||||
if (data.hasWindowAI) {
|
||||
prompts = [];
|
||||
summarizer?.destroy();
|
||||
(session as AILanguageModel)?.destroy();
|
||||
|
||||
const config: AILanguageModelCreateOptionsWithSystemPrompt = {
|
||||
|
|
@ -95,6 +100,16 @@ globalThis.addEventListener(
|
|||
let retry = 0;
|
||||
|
||||
try {
|
||||
if (
|
||||
data.hasWindowAI &&
|
||||
data.summarizeText &&
|
||||
"summarizer" in globalThis.ai &&
|
||||
(await globalThis.ai.summarizer.capabilities())?.available ===
|
||||
"readily"
|
||||
) {
|
||||
summarizer = await globalThis.ai.summarizer.create();
|
||||
}
|
||||
|
||||
while (retry++ < 3 && !response) {
|
||||
if (cancel) break;
|
||||
|
||||
|
|
@ -102,10 +117,33 @@ globalThis.addEventListener(
|
|||
if (data.hasWindowAI) {
|
||||
const aiAssistant = session as AILanguageModel;
|
||||
|
||||
if (summarizer && data.summarizeText) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
response = await summarizer.summarize(data.summarizeText);
|
||||
|
||||
(session as AILanguageModel)?.destroy();
|
||||
|
||||
prompts.push(
|
||||
{ content: data.text, role: "user" },
|
||||
{ content: response, role: "assistant" }
|
||||
);
|
||||
|
||||
const config: AILanguageModelCreateOptionsWithSystemPrompt = {
|
||||
...CONVO_STYLE_TEMPS[data.style],
|
||||
initialPrompts: [
|
||||
SYSTEM_PROMPT as unknown as AILanguageModelAssistantPrompt,
|
||||
...prompts,
|
||||
],
|
||||
};
|
||||
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
session = await globalThis.ai.languageModel.create(config);
|
||||
} else if (aiAssistant) {
|
||||
response = data.streamId
|
||||
? aiAssistant?.promptStreaming(data.text)
|
||||
? aiAssistant.promptStreaming(data.text)
|
||||
: // eslint-disable-next-line no-await-in-loop
|
||||
(await aiAssistant?.prompt(data.text)) || "";
|
||||
(await aiAssistant.prompt(data.text)) || "";
|
||||
}
|
||||
} else {
|
||||
(session as ChatCompletionMessageParam[]).push({
|
||||
content: data.text,
|
||||
|
|
@ -143,7 +181,7 @@ globalThis.addEventListener(
|
|||
markedLoaded = true;
|
||||
}
|
||||
|
||||
const sendMessage = (message: string, streamId?: number): void =>
|
||||
const sendMessage = (message: string, streamId?: number): void => {
|
||||
globalThis.postMessage({
|
||||
formattedResponse: globalThis.marked.parse(message, {
|
||||
headerIds: false,
|
||||
|
|
@ -152,8 +190,13 @@ globalThis.addEventListener(
|
|||
response: message,
|
||||
streamId,
|
||||
});
|
||||
prompts.push(
|
||||
{ content: data.text, role: "user" },
|
||||
{ content: message, role: "assistant" }
|
||||
);
|
||||
};
|
||||
|
||||
if (typeof response === "string") {
|
||||
if (response && typeof response === "string") {
|
||||
sendMessage(response);
|
||||
} else {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -4,11 +4,17 @@ import { type MarkedOptions } from "components/apps/Marked/useMarked";
|
|||
|
||||
declare global {
|
||||
/* eslint-disable vars-on-top, no-var */
|
||||
var ai: { languageModel: AILanguageModelFactory };
|
||||
var ai: {
|
||||
languageModel: AILanguageModelFactory;
|
||||
summarizer: AISummarizerFactory;
|
||||
};
|
||||
var marked: {
|
||||
parse: (markdownString: string, options: MarkedOptions) => string;
|
||||
};
|
||||
/* eslint-enable vars-on-top, no-var */
|
||||
interface Window {
|
||||
initialAiPrompt?: string;
|
||||
}
|
||||
}
|
||||
|
||||
export type MessageTypes = "user" | "ai";
|
||||
|
|
@ -26,6 +32,7 @@ export type WorkerMessage = {
|
|||
id: number;
|
||||
streamId?: number;
|
||||
style: ConvoStyles;
|
||||
summarizeText?: string;
|
||||
text: string;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -133,6 +133,14 @@ export const TEXT_EDITORS = ["MonacoEditor", "Vim"];
|
|||
|
||||
export const CURSOR_FILE_EXTENSIONS = new Set([".ani", ".cur"]);
|
||||
|
||||
export const TEXT_FILE_EXTENSIONS = new Set([
|
||||
".html",
|
||||
".htm",
|
||||
".whtml",
|
||||
".md",
|
||||
".txt",
|
||||
]);
|
||||
|
||||
export const EDITABLE_IMAGE_FILE_EXTENSIONS = new Set([
|
||||
".bmp",
|
||||
".gif",
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user