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,
|
SHORTCUT_EXTENSION,
|
||||||
SPREADSHEET_FORMATS,
|
SPREADSHEET_FORMATS,
|
||||||
TEXT_EDITORS,
|
TEXT_EDITORS,
|
||||||
|
TEXT_FILE_EXTENSIONS,
|
||||||
VIDEO_FILE_EXTENSIONS,
|
VIDEO_FILE_EXTENSIONS,
|
||||||
} from "utils/constants";
|
} from "utils/constants";
|
||||||
import {
|
import {
|
||||||
|
|
@ -52,6 +53,12 @@ import {
|
||||||
} from "utils/imagemagick/formats";
|
} from "utils/imagemagick/formats";
|
||||||
import { type ImageMagickConvertFile } from "utils/imagemagick/types";
|
import { type ImageMagickConvertFile } from "utils/imagemagick/types";
|
||||||
import { Share } from "components/system/Menu/MenuIcons";
|
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;
|
const { alias } = PACKAGE_DATA;
|
||||||
|
|
||||||
|
|
@ -92,6 +99,7 @@ const useFileContextMenu = (
|
||||||
updateFolder,
|
updateFolder,
|
||||||
} = useFileSystem();
|
} = useFileSystem();
|
||||||
const { contextMenu } = useMenu();
|
const { contextMenu } = useMenu();
|
||||||
|
const hasWindowAI = useWindowAI();
|
||||||
const { onContextMenuCapture, ...contextMenuHandlers } = useMemo(
|
const { onContextMenuCapture, ...contextMenuHandlers } = useMemo(
|
||||||
() =>
|
() =>
|
||||||
contextMenu?.(() => {
|
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 (
|
if (
|
||||||
hasBackgroundVideoExtension ||
|
hasBackgroundVideoExtension ||
|
||||||
(IMAGE_FILE_EXTENSIONS.has(pathExtension) &&
|
(IMAGE_FILE_EXTENSIONS.has(pathExtension) &&
|
||||||
|
|
@ -610,6 +647,7 @@ const useFileContextMenu = (
|
||||||
extractFiles,
|
extractFiles,
|
||||||
fileManagerId,
|
fileManagerId,
|
||||||
focusedEntries,
|
focusedEntries,
|
||||||
|
hasWindowAI,
|
||||||
isFocusedEntry,
|
isFocusedEntry,
|
||||||
lstat,
|
lstat,
|
||||||
mapFs,
|
mapFs,
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { extname } from "path";
|
||||||
import { useTheme } from "styled-components";
|
import { useTheme } from "styled-components";
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import {
|
import {
|
||||||
|
|
@ -18,7 +19,7 @@ import {
|
||||||
} from "components/system/Taskbar/AI/icons";
|
} from "components/system/Taskbar/AI/icons";
|
||||||
import useAITransition from "components/system/Taskbar/AI/useAITransition";
|
import useAITransition from "components/system/Taskbar/AI/useAITransition";
|
||||||
import {
|
import {
|
||||||
AI_STAGE,
|
AI_DISPLAY_TITLE,
|
||||||
AI_TITLE,
|
AI_TITLE,
|
||||||
AI_WORKER,
|
AI_WORKER,
|
||||||
DEFAULT_CONVO_STYLE,
|
DEFAULT_CONVO_STYLE,
|
||||||
|
|
@ -41,6 +42,7 @@ import useWorker from "hooks/useWorker";
|
||||||
import useFocusable from "components/system/Window/useFocusable";
|
import useFocusable from "components/system/Window/useFocusable";
|
||||||
import { useSession } from "contexts/session";
|
import { useSession } from "contexts/session";
|
||||||
import { useWindowAI } from "hooks/useWindowAI";
|
import { useWindowAI } from "hooks/useWindowAI";
|
||||||
|
import { useFileSystem } from "contexts/fileSystem";
|
||||||
|
|
||||||
type AIChatProps = {
|
type AIChatProps = {
|
||||||
toggleAI: () => void;
|
toggleAI: () => void;
|
||||||
|
|
@ -62,7 +64,7 @@ const AIChat: FC<AIChatProps> = ({ toggleAI }) => {
|
||||||
const [convoStyle, setConvoStyle] = useState(DEFAULT_CONVO_STYLE);
|
const [convoStyle, setConvoStyle] = useState(DEFAULT_CONVO_STYLE);
|
||||||
const [primaryColor, secondaryColor, tertiaryColor] =
|
const [primaryColor, secondaryColor, tertiaryColor] =
|
||||||
taskbarColor.ai[convoStyle];
|
taskbarColor.ai[convoStyle];
|
||||||
const [promptText, setPromptText] = useState("");
|
const [promptText, setPromptText] = useState(window.initialAiPrompt || "");
|
||||||
const textAreaRef = useRef<HTMLTextAreaElement>(null);
|
const textAreaRef = useRef<HTMLTextAreaElement>(null);
|
||||||
const sectionRef = useRef<HTMLDivElement>(null);
|
const sectionRef = useRef<HTMLDivElement>(null);
|
||||||
const typing = promptText.length > 0;
|
const typing = promptText.length > 0;
|
||||||
|
|
@ -155,6 +157,45 @@ const AIChat: FC<AIChatProps> = ({ toggleAI }) => {
|
||||||
textArea.style.height = "auto";
|
textArea.style.height = "auto";
|
||||||
textArea.style.height = `${textArea.scrollHeight}px`;
|
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(() => {
|
useEffect(() => {
|
||||||
textAreaRef.current?.focus(PREVENT_SCROLL);
|
textAreaRef.current?.focus(PREVENT_SCROLL);
|
||||||
|
|
@ -192,21 +233,16 @@ const AIChat: FC<AIChatProps> = ({ toggleAI }) => {
|
||||||
conversation.length > 0 &&
|
conversation.length > 0 &&
|
||||||
conversation[conversation.length - 1].type === "user"
|
conversation[conversation.length - 1].type === "user"
|
||||||
) {
|
) {
|
||||||
const { text } = conversation[conversation.length - 1];
|
sendMessage();
|
||||||
|
|
||||||
setResponding(true);
|
|
||||||
|
|
||||||
sessionIdRef.current ||= Date.now();
|
|
||||||
|
|
||||||
aiWorker.current.postMessage({
|
|
||||||
hasWindowAI,
|
|
||||||
id: sessionIdRef.current,
|
|
||||||
streamId: STREAMING_SUPPORT ? conversation.length : undefined,
|
|
||||||
style: convoStyle,
|
|
||||||
text,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}, [aiWorker, conversation, convoStyle, hasWindowAI]);
|
}, [aiWorker, conversation, sendMessage]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (window.initialAiPrompt && aiWorker.current) {
|
||||||
|
window.initialAiPrompt = "";
|
||||||
|
addUserPrompt();
|
||||||
|
}
|
||||||
|
}, [addUserPrompt, aiWorker]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const workerRef = aiWorker.current;
|
const workerRef = aiWorker.current;
|
||||||
|
|
@ -259,7 +295,7 @@ const AIChat: FC<AIChatProps> = ({ toggleAI }) => {
|
||||||
>
|
>
|
||||||
<div className="header">
|
<div className="header">
|
||||||
<header>
|
<header>
|
||||||
{`${AI_TITLE} (${AI_STAGE})`}
|
{AI_DISPLAY_TITLE}
|
||||||
<nav>
|
<nav>
|
||||||
<Button
|
<Button
|
||||||
className="close"
|
className="close"
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,9 @@ let responding = false;
|
||||||
|
|
||||||
let sessionId = 0;
|
let sessionId = 0;
|
||||||
let session: AILanguageModel | ChatCompletionMessageParam[] | undefined;
|
let session: AILanguageModel | ChatCompletionMessageParam[] | undefined;
|
||||||
|
let summarizer: AISummarizer | undefined;
|
||||||
|
let prompts: (AILanguageModelAssistantPrompt | AILanguageModelUserPrompt)[] =
|
||||||
|
[];
|
||||||
let engine: MLCEngine;
|
let engine: MLCEngine;
|
||||||
|
|
||||||
let markedLoaded = false;
|
let markedLoaded = false;
|
||||||
|
|
@ -67,6 +70,8 @@ globalThis.addEventListener(
|
||||||
sessionId = data.id;
|
sessionId = data.id;
|
||||||
|
|
||||||
if (data.hasWindowAI) {
|
if (data.hasWindowAI) {
|
||||||
|
prompts = [];
|
||||||
|
summarizer?.destroy();
|
||||||
(session as AILanguageModel)?.destroy();
|
(session as AILanguageModel)?.destroy();
|
||||||
|
|
||||||
const config: AILanguageModelCreateOptionsWithSystemPrompt = {
|
const config: AILanguageModelCreateOptionsWithSystemPrompt = {
|
||||||
|
|
@ -95,6 +100,16 @@ globalThis.addEventListener(
|
||||||
let retry = 0;
|
let retry = 0;
|
||||||
|
|
||||||
try {
|
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) {
|
while (retry++ < 3 && !response) {
|
||||||
if (cancel) break;
|
if (cancel) break;
|
||||||
|
|
||||||
|
|
@ -102,10 +117,33 @@ globalThis.addEventListener(
|
||||||
if (data.hasWindowAI) {
|
if (data.hasWindowAI) {
|
||||||
const aiAssistant = session as AILanguageModel;
|
const aiAssistant = session as AILanguageModel;
|
||||||
|
|
||||||
response = data.streamId
|
if (summarizer && data.summarizeText) {
|
||||||
? aiAssistant?.promptStreaming(data.text)
|
// eslint-disable-next-line no-await-in-loop
|
||||||
: // eslint-disable-next-line no-await-in-loop
|
response = await summarizer.summarize(data.summarizeText);
|
||||||
(await aiAssistant?.prompt(data.text)) || "";
|
|
||||||
|
(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)
|
||||||
|
: // eslint-disable-next-line no-await-in-loop
|
||||||
|
(await aiAssistant.prompt(data.text)) || "";
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
(session as ChatCompletionMessageParam[]).push({
|
(session as ChatCompletionMessageParam[]).push({
|
||||||
content: data.text,
|
content: data.text,
|
||||||
|
|
@ -143,7 +181,7 @@ globalThis.addEventListener(
|
||||||
markedLoaded = true;
|
markedLoaded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sendMessage = (message: string, streamId?: number): void =>
|
const sendMessage = (message: string, streamId?: number): void => {
|
||||||
globalThis.postMessage({
|
globalThis.postMessage({
|
||||||
formattedResponse: globalThis.marked.parse(message, {
|
formattedResponse: globalThis.marked.parse(message, {
|
||||||
headerIds: false,
|
headerIds: false,
|
||||||
|
|
@ -152,8 +190,13 @@ globalThis.addEventListener(
|
||||||
response: message,
|
response: message,
|
||||||
streamId,
|
streamId,
|
||||||
});
|
});
|
||||||
|
prompts.push(
|
||||||
|
{ content: data.text, role: "user" },
|
||||||
|
{ content: message, role: "assistant" }
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
if (typeof response === "string") {
|
if (response && typeof response === "string") {
|
||||||
sendMessage(response);
|
sendMessage(response);
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,17 @@ import { type MarkedOptions } from "components/apps/Marked/useMarked";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
/* eslint-disable vars-on-top, no-var */
|
/* eslint-disable vars-on-top, no-var */
|
||||||
var ai: { languageModel: AILanguageModelFactory };
|
var ai: {
|
||||||
|
languageModel: AILanguageModelFactory;
|
||||||
|
summarizer: AISummarizerFactory;
|
||||||
|
};
|
||||||
var marked: {
|
var marked: {
|
||||||
parse: (markdownString: string, options: MarkedOptions) => string;
|
parse: (markdownString: string, options: MarkedOptions) => string;
|
||||||
};
|
};
|
||||||
/* eslint-enable vars-on-top, no-var */
|
/* eslint-enable vars-on-top, no-var */
|
||||||
|
interface Window {
|
||||||
|
initialAiPrompt?: string;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MessageTypes = "user" | "ai";
|
export type MessageTypes = "user" | "ai";
|
||||||
|
|
@ -26,6 +32,7 @@ export type WorkerMessage = {
|
||||||
id: number;
|
id: number;
|
||||||
streamId?: number;
|
streamId?: number;
|
||||||
style: ConvoStyles;
|
style: ConvoStyles;
|
||||||
|
summarizeText?: string;
|
||||||
text: string;
|
text: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -133,6 +133,14 @@ export const TEXT_EDITORS = ["MonacoEditor", "Vim"];
|
||||||
|
|
||||||
export const CURSOR_FILE_EXTENSIONS = new Set([".ani", ".cur"]);
|
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([
|
export const EDITABLE_IMAGE_FILE_EXTENSIONS = new Set([
|
||||||
".bmp",
|
".bmp",
|
||||||
".gif",
|
".gif",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user