summaryrefslogtreecommitdiff
path: root/app/src/lib/categorization.ts
blob: a692240c34d4c820a276a482a442517784cb2f33 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
// import { Database } from "bun:sqlite";
// const db = new Database("data/bookmarks.db", { create: true });
import { TwitterBookmark } from "./twitter-api";

export interface CategorySuggestion {
  categories: string[];
  confidence: number;
  reasoning: string;
}

export interface CategorizationRequest {
  bookmark: TwitterBookmark;
  userCategories: Array<{ name: string; criteria: string }>;
}

export interface CategorizationResponse {
  suggestedCategories: CategorySuggestion[];
  newCategories: string[];
  summary: string;
  keyTopics: string[];
}

export interface UserCategories {
  categories: string[];
}

export const userCategories = [
  { name: "AI", criteria: "AI, machine learning, LLMs, all AI related stuff" },
  { name: "Nix", criteria: "Nix and NixOS" },
  { name: "Urbit", criteria: "anything related to Urbit" },
  {
    name: "Code",
    criteria:
      "other stuff related to software or software development (excludes the previous)",
  },
  { name: "history", criteria: "posts about history or archeology" },
  { name: "Politics", criteria: "posts about politics" },
  {
    name: "China",
    criteria:
      "posts about China or in Chinese which don't fall under any other category",
  },
  {
    name: "Japan",
    criteria:
      "posts about Japan or in Japanese which don't fall under any other category",
  },
  { name: "Movies", criteria: "posts related to movies, anime or TV shows" },
  { name: "Demographics", criteria: "posts about demographics or birth rates" },
  { name: "Memes", criteria: "posts including memes or otherwise funny" },
];

type FileTree = Map<string, FileEntry[]>;
type FileEntry = { file: string } | { dir: FileTree };
// type FileTree = { dir: `${string}/`; files: string[]; dirs?: Record<string, FileTree> };

class Obsidian {
  auth = `Bearer ${Bun.env.OBSIDIAN_API_KEY!}`;
  url = `http://127.0.0.1:27123`;
  tree: FileTree = new Map();
  async getFiles() {}
  async list() {
    this.populate(this.tree, "/");
  }
  async populate(parent: FileTree, dir: `${string}/`) {
    const entries: FileEntry[] = [];
    const res = (await this.call("/vault/" + dir)) as { files: string[] };
    for (let entry of res.files) {
      if (entry.endsWith(".md")) entries.push({ file: entry });
      else if (entry.endsWith("/")) this.populate(new Map(), entry as any);
      // entries.push({ dir: new Map() });
    }
    parent.set(dir, entries);
  }
  async call(path: string) {
    const headers = { Authorization: this.auth };
    const opts = { headers };
    const res = await fetch(this.url + path, opts);
    const j = await res.json();
    return j;
  }
  async post(path: string, body: string) {
    const headers = { Authorization: this.auth };
    const opts = { headers, method: "POST", body };
    const res = await fetch(this.url + path, opts);
    return res;
  }
  async saveToDisk(
    bookmark: TwitterBookmark,
    categorization: CategorizationResponse,
    tags: string[],
  ) {
    // append to file
    const body = `\n\n`;
    const opts = { method: "POST", headers, body };
    const res = await this.post("/vault/", body);
    if (!res.ok) {
      const j = await res.json();
      return { error: j.message };
    } else return { ok: "" };
  }
}