aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/App.tsx51
-rw-r--r--src/components/CommandPallete.tsx96
-rw-r--r--src/components/OllamaCommandPallete.tsx54
-rw-r--r--src/components/PromptAI.tsx23
-rw-r--r--src/components/ui/command.tsx153
-rw-r--r--src/components/ui/dialog.tsx120
-rw-r--r--src/components/ui/input.tsx25
-rw-r--r--src/index.css72
-rw-r--r--src/lib/utils.ts6
-rw-r--r--src/main.tsx24
-rw-r--r--src/ollama.tsx251
11 files changed, 686 insertions, 189 deletions
diff --git a/src/App.tsx b/src/App.tsx
index 5b5fca0..c88108d 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,30 +1,51 @@
+import { AppUserInfo } from "@logseq/libs/dist/LSPlugin";
import React, { useEffect, useRef, useState } from "react";
-import CommandPalette from "./components/CommandPallete";
+import { OllamaCommandPallete } from "./components/OllamaCommandPallete";
+import { convertToFlashCardFromEvent, DivideTaskIntoSubTasksFromEvent, ollamaUI } from "./ollama";
import { useAppVisible } from "./utils";
const options = [
- { label: 'Ask Ai' },
- { label: 'Define' },
- { label: 'Divide into subtasks' },
- { label: 'Summarize' },
+ 'Ask ai',
+ 'Ask with context',
+ 'Define',
+ 'Divide into subtasks',
+ 'Summarize',
+ 'Convert to flash card',
];
-
-async function ollamaUI() {
- console.log("Hello")
- logseq.showMainUI({ autoFocus: true })
- setTimeout(() => {
- document.getElementById("ai-input")?.focus()
- console.log(document.getElementById("ai-input"))
- }, 300)
+async function getTheme() {
+ const theme = await logseq.App.getUserInfo()
+ if (!theme) {
+ return "dark"
+ }
+ return theme.preferredThemeMode
}
function App() {
const innerRef = useRef<HTMLDivElement>(null);
const visible = useAppVisible();
+ const [theme, setTheme] = useState<string>('')
useEffect(() => {
+ const getTheme = async () => {
+ const theme = await logseq.App.getUserConfigs()
+ if (!theme) {
+ setTheme('dark')
+ } else {
+ setTheme(theme.preferredThemeMode)
+ }
+ }
+ getTheme();
+ if (!logseq.settings) {
+ return
+ }
logseq.Editor.registerSlashCommand("ollama", ollamaUI)
+ logseq.Editor.registerBlockContextMenuItem("Create a flash card", convertToFlashCardFromEvent)
+ logseq.Editor.registerBlockContextMenuItem("Make task into subtasks", DivideTaskIntoSubTasksFromEvent)
+ logseq.App.registerCommandShortcut(
+ { "binding": logseq.settings.shortcut },
+ ollamaUI
+ );
}, [])
if (visible) {
@@ -37,8 +58,8 @@ function App() {
}
}}
>
- <div ref={innerRef} className="text-white text-2xl">
- <CommandPalette options={options} />
+ <div ref={innerRef} className="flex items-center justify-center w-screen">
+ <OllamaCommandPallete options={options} theme={theme} />
</div>
</main>
);
diff --git a/src/components/CommandPallete.tsx b/src/components/CommandPallete.tsx
deleted file mode 100644
index e4f5924..0000000
--- a/src/components/CommandPallete.tsx
+++ /dev/null
@@ -1,96 +0,0 @@
-import React, { useState, useEffect, useRef } from 'react';
-import { DivideTaskIntoSubTasks, summarize } from '../ollama';
-import { PromptAI } from './PromptAI';
-
-
-function CommandPalette({ options }) {
- console.log("rendered commana pallate")
- const [inputValue, setInputValue] = useState('');
- const [selectedOption, setSelectedOption] = useState<{ label: string }>({ label: "Ask Ai" });
- const [filteredOptions, setFilteredOptions] = useState(options);
- const [isExecute, setIsExecute] = useState(false)
- const inputRef = useRef(null);
-
- useEffect(() => {
- // Initially, select the first option.
- if (filteredOptions.length > 0) {
- setSelectedOption(filteredOptions[0]);
- }
- }, [filteredOptions]);
-
-
- const handleInputChange = (e) => {
- const query = e.target.value;
- setInputValue(query);
-
- // Filter options based on the input.
- const results = options.filter((option: { label: string }) =>
- option.label.toLowerCase().includes(query.toLowerCase())
- );
- setFilteredOptions(results);
- };
-
- const handleKeyDown = (e: KeyboardEvent) => {
- if (e.key === 'ArrowUp' || e.key === 'ArrowDown' || e.key === 'Tab') {
- e.preventDefault();
-
- const currentIndex = filteredOptions.indexOf(selectedOption);
- let newIndex = currentIndex;
-
- if (e.key === 'ArrowUp' || (e.shiftKey && e.key == 'Tab')) {
- newIndex = (currentIndex - 1 + filteredOptions.length) % filteredOptions.length;
- } else if (e.key === 'ArrowDown' || e.key === 'Tab') {
- newIndex = (currentIndex + 1) % filteredOptions.length;
- }
-
- setSelectedOption(filteredOptions[newIndex]);
- } else if (e.key === 'Enter') {
- if (selectedOption) {
- setIsExecute(true)
- setInputValue(selectedOption.label);
- if (selectedOption.label === "Divide into subtasks") {
- DivideTaskIntoSubTasks()
- } else if (selectedOption.label === "Summarize") {
- summarize()
- }
- }
- }
- };
-
- return (
- isExecute && inputValue == "Ask Ai" ? (
- <PromptAI type="prompt" />
- ) : isExecute && inputValue === "Define" ? (
- <PromptAI type="define" />
- ) : !isExecute ? (
- <div className='w-screen flex items-center justify-center'>
- <div className="rounded-2xl bg-gray-800 text-white p-4 dark:bg-slate-900 dark:text-gray-100 w-3/4">
- <input
- ref={inputRef}
- type="text"
- placeholder="AI action..."
- value={inputValue}
- onChange={handleInputChange}
- onKeyDown={handleKeyDown}
- id="ai-input"
- className="bg-gray-700 text-white px-2 py-1 rounded-md dark:bg-gray-800 w-full"
- />
- <ul className="mt-2 max-h-90 overflow-y-auto">
- {filteredOptions.map((option: { label: string }, index: number) => (
- <li
- key={index}
- onClick={() => setSelectedOption(option)}
- className={`p-2 cursor-pointer ${selectedOption === option ? 'bg-blue-600 text-white border-2 border-blue-500' : ''
- } hover:bg-gray-600`}
- >
- {option.label}
- </li>
- ))}
- </ul>
- </div>
- </div>
- ) : null
- );
-}
-
-export default CommandPalette;
diff --git a/src/components/OllamaCommandPallete.tsx b/src/components/OllamaCommandPallete.tsx
new file mode 100644
index 0000000..d48d392
--- /dev/null
+++ b/src/components/OllamaCommandPallete.tsx
@@ -0,0 +1,54 @@
+import React, { KeyboardEventHandler, useEffect, useState } from "react"
+import {
+ Command,
+ CommandEmpty,
+ CommandInput,
+ CommandItem,
+ CommandList,
+} from "@/components/ui/command"
+import { convertToFlashCard, DivideTaskIntoSubTasks, summarize } from "@/ollama";
+import { PromptAI } from "./PromptAI";
+
+export function OllamaCommandPallete({ options, theme }: { options: string[], theme: string }) {
+ const [selection, setSelection] = useState('')
+ const [isEnterPressed, setIsEnterPressed] = useState(false);
+ const handleSelection = (selection: string) => {
+ setSelection(selection)
+ setIsEnterPressed(true);
+ switch (selection) {
+ case "divide into subtasks":
+ DivideTaskIntoSubTasks()
+ break;
+ case "summarize":
+ summarize()
+ break;
+ case "convert to flash card":
+ convertToFlashCard()
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (isEnterPressed && (selection !== 'ask ai' && selection !== 'define' && selection !== 'ask with context')) {
+ return null
+ }
+
+ return (
+ selection === 'ask with context' || selection === 'ask ai' || selection === 'define' ? (<PromptAI theme={theme} type={selection} />) : (
+ <Command className={(theme === 'dark' ? "dark dark:bg-gray-900" : "bg-gray-200") + " rounded-lg border shadow-md w-1/2"}>
+ <CommandInput className="ai-input" placeholder="Type a command or search..." />
+ <CommandList>
+ <CommandEmpty>No results found.</CommandEmpty>
+ {
+ options.map((option) => (
+ <CommandItem key={option} onSelect={handleSelection} className="text-lg">
+ <span>{option}</span>
+ </CommandItem>
+ ))
+ }
+ </CommandList>
+ </Command>
+ )
+ )
+}
diff --git a/src/components/PromptAI.tsx b/src/components/PromptAI.tsx
index 7a6b361..0b87917 100644
--- a/src/components/PromptAI.tsx
+++ b/src/components/PromptAI.tsx
@@ -1,28 +1,31 @@
-import React, { useEffect, useRef, useState } from 'react'
-import { askAI, defineWord, DivideTaskIntoSubTasks } from '../ollama';
+import React, { KeyboardEventHandler, useEffect, useState } from 'react'
+import { askAI, askWithContext, defineWord } from '../ollama';
+import { Input } from '@/components/ui/input';
-export const PromptAI = ({ type }) => {
+export const PromptAI = ({ type, theme }: { type: string, theme: string }) => {
- const placeholder = type === 'prompt' ? "Prompt..." : "Define..."
+ const placeholder = type === 'ask ai' ? "Prompt..." : "Define..."
const [inputValue, setInputValue] = useState('');
const [hitEnter, setHitEnter] = useState(false)
useEffect(() => {
if (hitEnter) {
- if (type === 'prompt') {
+ if (type === 'ask ai') {
askAI(inputValue)
- } else {
+ } else if (type === 'define') {
defineWord(inputValue)
+ } else if (type === 'ask with context') {
+ askWithContext(inputValue)
}
}
}, [hitEnter])
- const handleInputChange = (e) => {
+ const handleInputChange = (e: any) => {
const query = e.target.value;
setInputValue(query);
};
- const handleKeyDown = (e) => {
+ const handleKeyDown: KeyboardEventHandler<HTMLDivElement> = (e) => {
if (e.key === 'Enter') {
setHitEnter(true)
}
@@ -30,14 +33,14 @@ export const PromptAI = ({ type }) => {
return (
!hitEnter ? (
<div className='w-screen text-center'>
- <input
+ <Input
autoFocus
type="text"
placeholder={placeholder}
value={inputValue}
onChange={handleInputChange}
onKeyDown={handleKeyDown}
- className="bg-gray-700 text-white px-2 py-1 rounded-md dark:bg-gray-800 inline-block w-3/4"
+ className={(theme === 'dark' ? "dark text-white dark:bg-gray-800" : "text-black bg-gray-200") + "px-2 py-1 rounded-md inline-block w-3/4"}
/>
</div>
) : null
diff --git a/src/components/ui/command.tsx b/src/components/ui/command.tsx
new file mode 100644
index 0000000..c283b7b
--- /dev/null
+++ b/src/components/ui/command.tsx
@@ -0,0 +1,153 @@
+import * as React from "react"
+import { DialogProps } from "@radix-ui/react-dialog"
+import { Command as CommandPrimitive } from "cmdk"
+import { Search } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+import { Dialog, DialogContent } from "@/components/ui/dialog"
+
+const Command = React.forwardRef<
+ React.ElementRef<typeof CommandPrimitive>,
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive>
+>(({ className, ...props }, ref) => (
+ <CommandPrimitive
+ ref={ref}
+ className={cn(
+ "flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
+ className
+ )}
+ {...props}
+ />
+))
+Command.displayName = CommandPrimitive.displayName
+
+interface CommandDialogProps extends DialogProps {}
+
+const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
+ return (
+ <Dialog {...props}>
+ <DialogContent className="overflow-hidden p-0 shadow-lg">
+ <Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
+ {children}
+ </Command>
+ </DialogContent>
+ </Dialog>
+ )
+}
+
+const CommandInput = React.forwardRef<
+ React.ElementRef<typeof CommandPrimitive.Input>,
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
+>(({ className, ...props }, ref) => (
+ <div className="flex items-center border-b px-3" cmdk-input-wrapper="">
+ <Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
+ <CommandPrimitive.Input
+ ref={ref}
+ className={cn(
+ "flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
+ className
+ )}
+ {...props}
+ />
+ </div>
+))
+
+CommandInput.displayName = CommandPrimitive.Input.displayName
+
+const CommandList = React.forwardRef<
+ React.ElementRef<typeof CommandPrimitive.List>,
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
+>(({ className, ...props }, ref) => (
+ <CommandPrimitive.List
+ ref={ref}
+ className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
+ {...props}
+ />
+))
+
+CommandList.displayName = CommandPrimitive.List.displayName
+
+const CommandEmpty = React.forwardRef<
+ React.ElementRef<typeof CommandPrimitive.Empty>,
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
+>((props, ref) => (
+ <CommandPrimitive.Empty
+ ref={ref}
+ className="py-6 text-center text-sm"
+ {...props}
+ />
+))
+
+CommandEmpty.displayName = CommandPrimitive.Empty.displayName
+
+const CommandGroup = React.forwardRef<
+ React.ElementRef<typeof CommandPrimitive.Group>,
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
+>(({ className, ...props }, ref) => (
+ <CommandPrimitive.Group
+ ref={ref}
+ className={cn(
+ "overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
+ className
+ )}
+ {...props}
+ />
+))
+
+CommandGroup.displayName = CommandPrimitive.Group.displayName
+
+const CommandSeparator = React.forwardRef<
+ React.ElementRef<typeof CommandPrimitive.Separator>,
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
+>(({ className, ...props }, ref) => (
+ <CommandPrimitive.Separator
+ ref={ref}
+ className={cn("-mx-1 h-px bg-border", className)}
+ {...props}
+ />
+))
+CommandSeparator.displayName = CommandPrimitive.Separator.displayName
+
+const CommandItem = React.forwardRef<
+ React.ElementRef<typeof CommandPrimitive.Item>,
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
+>(({ className, ...props }, ref) => (
+ <CommandPrimitive.Item
+ ref={ref}
+ className={cn(
+ "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none aria-selected:bg-accent aria-selected:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
+ className
+ )}
+ {...props}
+ />
+))
+
+CommandItem.displayName = CommandPrimitive.Item.displayName
+
+const CommandShortcut = ({
+ className,
+ ...props
+}: React.HTMLAttributes<HTMLSpanElement>) => {
+ return (
+ <span
+ className={cn(
+ "ml-auto text-xs tracking-widest text-muted-foreground",
+ className
+ )}
+ {...props}
+ />
+ )
+}
+CommandShortcut.displayName = "CommandShortcut"
+
+export {
+ Command,
+ CommandDialog,
+ CommandInput,
+ CommandList,
+ CommandEmpty,
+ CommandGroup,
+ CommandItem,
+ CommandShortcut,
+ CommandSeparator,
+}
diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx
new file mode 100644
index 0000000..097fe47
--- /dev/null
+++ b/src/components/ui/dialog.tsx
@@ -0,0 +1,120 @@
+import * as React from "react"
+import * as DialogPrimitive from "@radix-ui/react-dialog"
+import { X } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const Dialog = DialogPrimitive.Root
+
+const DialogTrigger = DialogPrimitive.Trigger
+
+const DialogPortal = DialogPrimitive.Portal
+
+const DialogClose = DialogPrimitive.Close
+
+const DialogOverlay = React.forwardRef<
+ React.ElementRef<typeof DialogPrimitive.Overlay>,
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
+>(({ className, ...props }, ref) => (
+ <DialogPrimitive.Overlay
+ ref={ref}
+ className={cn(
+ "fixed inset-0 z-50 bg-background/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
+ className
+ )}
+ {...props}
+ />
+))
+DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
+
+const DialogContent = React.forwardRef<
+ React.ElementRef<typeof DialogPrimitive.Content>,
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
+>(({ className, children, ...props }, ref) => (
+ <DialogPortal>
+ <DialogOverlay />
+ <DialogPrimitive.Content
+ ref={ref}
+ className={cn(
+ "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg md:w-full",
+ className
+ )}
+ {...props}
+ >
+ {children}
+ <DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
+ <X className="h-4 w-4" />
+ <span className="sr-only">Close</span>
+ </DialogPrimitive.Close>
+ </DialogPrimitive.Content>
+ </DialogPortal>
+))
+DialogContent.displayName = DialogPrimitive.Content.displayName
+
+const DialogHeader = ({
+ className,
+ ...props
+}: React.HTMLAttributes<HTMLDivElement>) => (
+ <div
+ className={cn(
+ "flex flex-col space-y-1.5 text-center sm:text-left",
+ className
+ )}
+ {...props}
+ />
+)
+DialogHeader.displayName = "DialogHeader"
+
+const DialogFooter = ({
+ className,
+ ...props
+}: React.HTMLAttributes<HTMLDivElement>) => (
+ <div
+ className={cn(
+ "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
+ className
+ )}
+ {...props}
+ />
+)
+DialogFooter.displayName = "DialogFooter"
+
+const DialogTitle = React.forwardRef<
+ React.ElementRef<typeof DialogPrimitive.Title>,
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
+>(({ className, ...props }, ref) => (
+ <DialogPrimitive.Title
+ ref={ref}
+ className={cn(
+ "text-lg font-semibold leading-none tracking-tight",
+ className
+ )}
+ {...props}
+ />
+))
+DialogTitle.displayName = DialogPrimitive.Title.displayName
+
+const DialogDescription = React.forwardRef<
+ React.ElementRef<typeof DialogPrimitive.Description>,
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
+>(({ className, ...props }, ref) => (
+ <DialogPrimitive.Description
+ ref={ref}
+ className={cn("text-sm text-muted-foreground", className)}
+ {...props}
+ />
+))
+DialogDescription.displayName = DialogPrimitive.Description.displayName
+
+export {
+ Dialog,
+ DialogPortal,
+ DialogOverlay,
+ DialogClose,
+ DialogTrigger,
+ DialogContent,
+ DialogHeader,
+ DialogFooter,
+ DialogTitle,
+ DialogDescription,
+}
diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx
new file mode 100644
index 0000000..677d05f
--- /dev/null
+++ b/src/components/ui/input.tsx
@@ -0,0 +1,25 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+export interface InputProps
+ extends React.InputHTMLAttributes<HTMLInputElement> {}
+
+const Input = React.forwardRef<HTMLInputElement, InputProps>(
+ ({ className, type, ...props }, ref) => {
+ return (
+ <input
+ type={type}
+ className={cn(
+ "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
+ className
+ )}
+ ref={ref}
+ {...props}
+ />
+ )
+ }
+)
+Input.displayName = "Input"
+
+export { Input }
diff --git a/src/index.css b/src/index.css
index a90f074..a20f5ca 100644
--- a/src/index.css
+++ b/src/index.css
@@ -1,4 +1,76 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
+
+@layer base {
+ :root {
+ --background: 0 0% 100%;
+ --foreground: 222.2 84% 4.9%;
+ --card: 0 0% 100%;
+ --card-foreground: 222.2 84% 4.9%;
+
+ --popover: 0 0% 100%;
+ --popover-foreground: 222.2 84% 4.9%;
+
+ --primary: 222.2 47.4% 11.2%;
+ --primary-foreground: 210 40% 98%;
+
+ --secondary: 210 40% 96.1%;
+ --secondary-foreground: 222.2 47.4% 11.2%;
+
+ --muted: 210 40% 96.1%;
+ --muted-foreground: 215.4 16.3% 46.9%;
+
+ --accent: 210 40% 96.1%;
+ --accent-foreground: 222.2 47.4% 11.2%;
+
+ --destructive: 0 84.2% 60.2%;
+ --destructive-foreground: 210 40% 98%;
+
+ --border: 214.3 31.8% 91.4%;
+ --input: 214.3 31.8% 91.4%;
+ --ring: 222.2 84% 4.9%;
+
+ --radius: 0.5rem;
+ }
+
+ .dark {
+ --background: 222.2 84% 4.9%;
+ --foreground: 210 40% 98%;
+
+ --card: 222.2 84% 4.9%;
+ --card-foreground: 210 40% 98%;
+
+ --popover: 222.2 84% 4.9%;
+ --popover-foreground: 210 40% 98%;
+
+ --primary: 210 40% 98%;
+ --primary-foreground: 222.2 47.4% 11.2%;
+
+ --secondary: 217.2 32.6% 17.5%;
+ --secondary-foreground: 210 40% 98%;
+
+ --muted: 217.2 32.6% 17.5%;
+ --muted-foreground: 215 20.2% 65.1%;
+
+ --accent: 217.2 32.6% 17.5%;
+ --accent-foreground: 210 40% 98%;
+
+ --destructive: 0 62.8% 30.6%;
+ --destructive-foreground: 210 40% 98%;
+
+ --border: 217.2 32.6% 17.5%;
+ --input: 217.2 32.6% 17.5%;
+ --ring: 212.7 26.8% 83.9%;
+ }
+}
+
+@layer base {
+ * {
+ @apply border-border;
+ }
+ /* body { */
+ /* @apply bg-background text-foreground; */
+ /* } */
+}
diff --git a/src/lib/utils.ts b/src/lib/utils.ts
new file mode 100644
index 0000000..ec79801
--- /dev/null
+++ b/src/lib/utils.ts
@@ -0,0 +1,6 @@
+import { type ClassValue, clsx } from "clsx"
+import { twMerge } from "tailwind-merge"
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs))
+}
diff --git a/src/main.tsx b/src/main.tsx
index c994e33..048b9ab 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -7,16 +7,13 @@ import "./index.css";
import { logseq as PL } from "../package.json";
import { SettingSchemaDesc } from "@logseq/libs/dist/LSPlugin";
+import { ollamaUI } from "./ollama";
// @ts-expect-error
const css = (t, ...args) => String.raw(t, ...args);
-const delay = (t = 100) => new Promise(r => setTimeout(r, t))
-
const pluginId = PL.id;
-
-
let settings: SettingSchemaDesc[] = [
{
key: "host",
@@ -32,13 +29,19 @@ let settings: SettingSchemaDesc[] = [
description: "Set your desired model to use ollama",
default: "mistral:instruct"
},
+ {
+ key: "shortcut",
+ type: "string",
+ title: "Shortcut",
+ description: "Shortcut to open plugin command pallete",
+ default: "mod+shift+o"
+ },
]
+
function main() {
- console.log("Hello")
console.info(`#${pluginId}: MAIN`);
- // logseq.useSettingsSchema(settings)
- let loading = false
+ logseq.useSettingsSchema(settings)
const root = ReactDOM.createRoot(document.getElementById("app")!);
root.render(
@@ -47,13 +50,10 @@ function main() {
</React.StrictMode>
);
- function show() {
- logseq.showMainUI();
- }
function createModel() {
return {
show() {
- logseq.showMainUI();
+ ollamaUI()
},
};
}
@@ -83,7 +83,7 @@ function main() {
template: `
<a data-on-click="show"
class="button">
- <i class="ti ti-brand-reddit"></i>
+ <i class="ti ti-wand"></i>
</a>
`,
});
diff --git a/src/ollama.tsx b/src/ollama.tsx
index a08c6e3..f6e9b97 100644
--- a/src/ollama.tsx
+++ b/src/ollama.tsx
@@ -1,57 +1,130 @@
-import { SettingSchemaDesc } from "@logseq/libs/dist/LSPlugin.user";
+import { IHookEvent } from "@logseq/libs/dist/LSPlugin.user";
+import { BlockEntity, BlockUUIDTuple } from "@logseq/libs/dist/LSPlugin.user";
const delay = (t = 100) => new Promise(r => setTimeout(r, t))
-let settings: SettingSchemaDesc[] = [
- {
- key: "host",
- type: "string",
- title: "Host",
- description: "Set the host of your ollama model",
- default: "localhost:11434"
- },
- {
- key: "model",
- type: "string",
- title: "LLM Model",
- description: "Set your desired model to use ollama",
- default: "mistral:instruct"
- },
-]
-
-async function promptLLM(url: string, prompt: string, model: string) {
-
-
- const response = await fetch('http://localhost:11434/api/generate', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({
- model: model,
- prompt: prompt,
- stream: false,
- }),
- })
- if (!response.ok) {
- throw new Error('Network response was not ok');
+export async function ollamaUI() {
+ logseq.showMainUI()
+ setTimeout(() => {
+ const element = document.querySelector(".ai-input") as HTMLInputElement | null;
+ if (element) {
+ element.focus();
+ }
+ }, 300)
+}
+
+function isBlockEntity(b: BlockEntity | BlockUUIDTuple): b is BlockEntity {
+ return (b as BlockEntity).uuid !== undefined;
+}
+
+async function getTreeContent(b: BlockEntity) {
+ let content = "";
+ const trimmedBlockContent = b.content.trim();
+ if (trimmedBlockContent.length > 0) {
+ content += trimmedBlockContent;
}
- const data = await response.json();
+ if (!b.children) {
+ return content;
+ }
+
+ for (const child of b.children) {
+ if (isBlockEntity(child)) {
+ content += await getTreeContent(child);
+ } else {
+ const childBlock = await logseq.Editor.getBlock(child[1], {
+ includeChildren: true,
+ });
+ if (childBlock) {
+ content += await getTreeContent(childBlock);
+ }
+ }
+ }
+ return content;
+}
+
+export async function getPageContentFromBlock(b: BlockEntity): Promise<string> {
+ let blockContents = [];
+
+ const currentBlock = await logseq.Editor.getBlock(b);
+ if (!currentBlock) {
+ throw new Error("Block not found");
+ }
+
+ const page = await logseq.Editor.getPage(currentBlock.page.id);
+ if (!page) {
+ throw new Error("Page not found");
+ }
+
+ const pageBlocks = await logseq.Editor.getPageBlocksTree(page.name);
+ for (const pageBlock of pageBlocks) {
+ const blockContent = await getTreeContent(pageBlock);
+ if (blockContent.length > 0) {
+ blockContents.push(blockContent);
+ }
+ }
+ return blockContents.join(" ");
+}
- return data.response;
+async function promptLLM(prompt: string) {
+ if (!logseq.settings) {
+ throw new Error("Couldn't find logseq settings");
+ }
+ try {
+ const response = await fetch(`http://${logseq.settings.host}/api/generate`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ model: logseq.settings.model,
+ prompt: prompt,
+ stream: false,
+ }),
+ })
+ if (!response.ok) {
+ console.log("Error: couldn't fulful request")
+ logseq.App.showMsg("Couldn't fulfuil request make sure you don't have a typo in the name of the model or the host url")
+ throw new Error('Network response was not ok');
+ }
+ const data = await response.json();
+ return data.response;
+ } catch (e: any) {
+ console.error("ERROR: ", e)
+ logseq.App.showMsg("Couldn't fulfuil request make sure you don't have a typo in the name of the model or the host url")
+ }
}
export async function defineWord(word: string) {
- askAI(`Define this following ${word}`)
+ askAI(`What's the formal defintion of ${word}`)
}
-export async function summarize() {
+
+export async function askWithContext(prompt: string) {
await delay(300)
try {
+ const currentBlocksTree = await logseq.Editor.getCurrentPageBlocksTree()
+ const currentBlock = currentBlocksTree[0]
+ if (!currentBlock) {
+ throw new Error("Block not found");
+ }
+ let blocksContent = ""
+ for (const block of currentBlocksTree) {
+ blocksContent += await getTreeContent(block)
+ }
+ askAI(`With the Context of : ${blocksContent}, ${prompt}`)
+ } catch (e: any) {
+ logseq.App.showMsg(e.toString(), 'warning')
+ console.error(e)
+ }
+}
+
+export async function summarize() {
+ await delay(300)
+ try {
const currentSelectedBlocks = await logseq.Editor.getCurrentPageBlocksTree()
let blocksContent = ""
if (currentSelectedBlocks) {
@@ -60,15 +133,12 @@ export async function summarize() {
blocksContent += block.content + "/n"
}
if (lastBlock) {
- lastBlock = await logseq.Editor.insertBlock(lastBlock.uuid, '🚀 Summarizing....', { before: false })
+ lastBlock = await logseq.Editor.insertBlock(lastBlock.uuid, '🚀 Summarizing....', { before: true })
}
-
- const summary = await promptLLM("localhost:11434", `Summarize the following ${blocksContent}`, "mistral:instruct")
-
+ const summary = await promptLLM(`Summarize the following ${blocksContent}`)
await logseq.Editor.updateBlock(lastBlock.uuid, `Summary: ${summary}`)
}
-
- } catch (e) {
+ } catch (e: any) {
logseq.App.showMsg(e.toString(), 'warning')
console.error(e)
}
@@ -76,7 +146,6 @@ export async function summarize() {
export async function askAI(prompt: string) {
await delay(300)
-
try {
const currentSelectedBlocks = await logseq.Editor.getCurrentPageBlocksTree()
if (currentSelectedBlocks) {
@@ -84,11 +153,87 @@ export async function askAI(prompt: string) {
if (lastBlock) {
lastBlock = await logseq.Editor.insertBlock(lastBlock.uuid, 'Generating....', { before: true })
}
- const response = await promptLLM("localhost:11434", prompt, "mistral:instruct")
+ const response = await promptLLM(prompt)
await logseq.Editor.updateBlock(lastBlock.uuid, response)
}
- } catch (e) {
+ } catch (e: any) {
+ logseq.App.showMsg(e.toString(), 'warning')
+ console.error(e)
+ }
+}
+
+
+export async function convertToFlashCardFromEvent(b: IHookEvent) {
+ try {
+ const currentBlock = await logseq.Editor.getBlock(b.uuid)
+ if (!currentBlock) {
+ throw new Error("Block not found");
+ }
+ const block = await logseq.Editor.insertBlock(currentBlock.uuid, 'Generating....', { before: false })
+ if (!block) {
+ throw new Error("Block not found");
+ }
+ const response = await promptLLM(`Create a flashcard for:\n ${currentBlock.content}`)
+ await logseq.Editor.updateBlock(block.uuid, `${response} #card`)
+ } catch (e: any) {
+ logseq.App.showMsg(e.toString(), 'warning')
+ console.error(e)
+ }
+}
+
+
+export async function convertToFlashCard() {
+ try {
+ const currentBlock = await logseq.Editor.getCurrentBlock()
+ if (!currentBlock) {
+ throw new Error("Block not found");
+ }
+ const block = await logseq.Editor.insertBlock(currentBlock.uuid, "Genearting todos....", { before: false })
+ if (!block) {
+ throw new Error("Block not found");
+ }
+ if (currentBlock) {
+ let i = 0;
+ const response = await promptLLM(`Divide this task into subtasks with numbers: ${currentBlock.content}`)
+ for (const todo of response.split("\n")) {
+ if (i == 0) {
+ await logseq.Editor.updateBlock(block.uuid, `TODO ${todo.slice(3)}`)