@hiai-gg/hiai-docs 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.all-contributorsrc +18 -0
- package/.claude/settings.local.json +61 -0
- package/.dockerignore +113 -0
- package/.env.example +68 -0
- package/.github/FUNDING.yml +5 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +74 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +78 -0
- package/.github/dependabot.yml +136 -0
- package/.github/pull_request_template.md +96 -0
- package/.github/workflows/ci.yml +283 -0
- package/AGENTS.md +237 -0
- package/CODE_OF_CONDUCT.md +134 -0
- package/CONTRIBUTING.md +77 -0
- package/Caddyfile +50 -0
- package/Dockerfile.backend +60 -0
- package/LICENSE +21 -0
- package/README.md +284 -0
- package/RELEASE_CHECKLIST.md +34 -0
- package/SECURITY.md +60 -0
- package/backend/package.json +43 -0
- package/backend/src/__tests__/auth-helpers.test.ts +51 -0
- package/backend/src/__tests__/chunker.test.ts +65 -0
- package/backend/src/__tests__/config.test.ts +91 -0
- package/backend/src/__tests__/csrf.test.ts +91 -0
- package/backend/src/__tests__/embedding.test.ts +48 -0
- package/backend/src/__tests__/rate-limit.test.ts +46 -0
- package/backend/src/__tests__/routes.test.ts +38 -0
- package/backend/src/__tests__/schema.test.ts +31 -0
- package/backend/src/__tests__/validation.test.ts +556 -0
- package/backend/src/api/middleware/auth.ts +56 -0
- package/backend/src/api/middleware/csrf.ts +91 -0
- package/backend/src/api/middleware/rate-limit.ts +77 -0
- package/backend/src/api/middleware/webhook-verify.ts +22 -0
- package/backend/src/api/routes/attachments.ts +280 -0
- package/backend/src/api/routes/auth.ts +52 -0
- package/backend/src/api/routes/collaboration.ts +121 -0
- package/backend/src/api/routes/documents.ts +664 -0
- package/backend/src/api/routes/folders.ts +226 -0
- package/backend/src/api/routes/search.ts +354 -0
- package/backend/src/api/routes/share.ts +512 -0
- package/backend/src/api/routes/tags.ts +247 -0
- package/backend/src/api/routes/versions.ts +99 -0
- package/backend/src/api/routes/webhooks.ts +43 -0
- package/backend/src/embedding/chunker.ts +74 -0
- package/backend/src/embedding/index.ts +117 -0
- package/backend/src/embedding/providers/ollama.ts +63 -0
- package/backend/src/embedding/providers/openrouter.ts +71 -0
- package/backend/src/embedding/utils.ts +13 -0
- package/backend/src/embedding/worker.ts +89 -0
- package/backend/src/index.ts +147 -0
- package/backend/src/lib/auth-helpers.ts +27 -0
- package/backend/src/lib/auth.ts +35 -0
- package/backend/src/lib/config.ts +73 -0
- package/backend/src/lib/db.ts +7 -0
- package/backend/src/lib/embedding-queue.ts +12 -0
- package/backend/src/lib/logger.ts +18 -0
- package/backend/src/lib/markdown-to-doc.ts +45 -0
- package/backend/src/lib/minio.ts +46 -0
- package/backend/src/lib/redis.ts +19 -0
- package/backend/src/lib/yjs-provider.ts +182 -0
- package/backend/tests/integration/_harness.ts +754 -0
- package/backend/tests/integration/auth.test.ts +296 -0
- package/backend/tests/integration/routes.documents.test.ts +459 -0
- package/backend/tests/integration/routes.folders.test.ts +337 -0
- package/backend/tests/integration/routes.search.test.ts +322 -0
- package/backend/tests/integration/routes.share.test.ts +773 -0
- package/backend/tests/integration/routes.tags.test.ts +425 -0
- package/backend/tests/integration/routes.versions.test.ts +233 -0
- package/backend/tsconfig.json +18 -0
- package/docker-compose.yml +218 -0
- package/docs/API.md +328 -0
- package/docs/ARCHITECTURE.md +75 -0
- package/docs/DEPLOYMENT.md +113 -0
- package/docs/PRODUCTION_STATUS.md +61 -0
- package/docs/openapi.json +385 -0
- package/frontend/.svelte-kit.old/ambient.d.ts +230 -0
- package/frontend/.svelte-kit.old/env.d.ts +1 -0
- package/frontend/.svelte-kit.old/generated/client/app.js +46 -0
- package/frontend/.svelte-kit.old/generated/client/matchers.js +1 -0
- package/frontend/.svelte-kit.old/generated/client/nodes/0.js +3 -0
- package/frontend/.svelte-kit.old/generated/client/nodes/1.js +1 -0
- package/frontend/.svelte-kit.old/generated/client/nodes/10.js +3 -0
- package/frontend/.svelte-kit.old/generated/client/nodes/2.js +1 -0
- package/frontend/.svelte-kit.old/generated/client/nodes/3.js +1 -0
- package/frontend/.svelte-kit.old/generated/client/nodes/4.js +1 -0
- package/frontend/.svelte-kit.old/generated/client/nodes/5.js +3 -0
- package/frontend/.svelte-kit.old/generated/client/nodes/6.js +1 -0
- package/frontend/.svelte-kit.old/generated/client/nodes/7.js +3 -0
- package/frontend/.svelte-kit.old/generated/client/nodes/8.js +1 -0
- package/frontend/.svelte-kit.old/generated/client/nodes/9.js +3 -0
- package/frontend/.svelte-kit.old/generated/root.js +3 -0
- package/frontend/.svelte-kit.old/generated/root.svelte +80 -0
- package/frontend/.svelte-kit.old/generated/server/internal.js +55 -0
- package/frontend/.svelte-kit.old/non-ambient.d.ts +59 -0
- package/frontend/.svelte-kit.old/tsconfig.json +59 -0
- package/frontend/.svelte-kit.old/types/route_meta_data.json +40 -0
- package/frontend/.svelte-kit.old/types/src/routes/$types.d.ts +21 -0
- package/frontend/.svelte-kit.old/types/src/routes/(app)/$types.d.ts +30 -0
- package/frontend/.svelte-kit.old/types/src/routes/(app)/docs/[id]/$types.d.ts +27 -0
- package/frontend/.svelte-kit.old/types/src/routes/(app)/docs/[id]/proxy+page.ts +25 -0
- package/frontend/.svelte-kit.old/types/src/routes/api/[...path]/$types.d.ts +10 -0
- package/frontend/.svelte-kit.old/types/src/routes/folders/[id]/$types.d.ts +27 -0
- package/frontend/.svelte-kit.old/types/src/routes/folders/[id]/proxy+page.ts +15 -0
- package/frontend/.svelte-kit.old/types/src/routes/login/$types.d.ts +17 -0
- package/frontend/.svelte-kit.old/types/src/routes/register/$types.d.ts +17 -0
- package/frontend/.svelte-kit.old/types/src/routes/s/[token]/$types.d.ts +20 -0
- package/frontend/.svelte-kit.old/types/src/routes/s/[token]/proxy+page.ts +6 -0
- package/frontend/.svelte-kit.old/types/src/routes/search/$types.d.ts +19 -0
- package/frontend/.svelte-kit.old/types/src/routes/search/proxy+page.ts +26 -0
- package/frontend/.svelte-kit.old/types/src/routes/settings/$types.d.ts +17 -0
- package/frontend/Dockerfile +44 -0
- package/frontend/biome.json +40 -0
- package/frontend/components.json +18 -0
- package/frontend/messages/en.json +434 -0
- package/frontend/package.json +70 -0
- package/frontend/project.inlang/settings.json +12 -0
- package/frontend/src/app.css +6 -0
- package/frontend/src/app.d.ts +13 -0
- package/frontend/src/app.html +30 -0
- package/frontend/src/hooks.server.ts +10 -0
- package/frontend/src/hooks.ts +10 -0
- package/frontend/src/lib/api/attachments.ts +45 -0
- package/frontend/src/lib/api/client.test.ts +15 -0
- package/frontend/src/lib/api/client.ts +57 -0
- package/frontend/src/lib/api/documents.ts +83 -0
- package/frontend/src/lib/api/folders.ts +180 -0
- package/frontend/src/lib/api/search.test.ts +52 -0
- package/frontend/src/lib/api/search.ts +128 -0
- package/frontend/src/lib/api/settings.ts +95 -0
- package/frontend/src/lib/api/share.ts +71 -0
- package/frontend/src/lib/api/tags.test.ts +91 -0
- package/frontend/src/lib/api/tags.ts +87 -0
- package/frontend/src/lib/auth-client.ts +10 -0
- package/frontend/src/lib/collaboration.ts +63 -0
- package/frontend/src/lib/components/AttachmentUpload.svelte +110 -0
- package/frontend/src/lib/components/DatePicker.svelte +322 -0
- package/frontend/src/lib/components/DocumentCard.svelte +166 -0
- package/frontend/src/lib/components/EmptyState.svelte +49 -0
- package/frontend/src/lib/components/FolderCard.svelte +93 -0
- package/frontend/src/lib/components/ScrollToTop.svelte +72 -0
- package/frontend/src/lib/components/SearchBar.svelte +47 -0
- package/frontend/src/lib/components/SearchResult.svelte +115 -0
- package/frontend/src/lib/components/SettingsDialog.svelte +271 -0
- package/frontend/src/lib/components/ShareDialog.svelte +158 -0
- package/frontend/src/lib/components/ShareLink.svelte +98 -0
- package/frontend/src/lib/components/TagCreateDialog.svelte +284 -0
- package/frontend/src/lib/components/VersionDiff.svelte +55 -0
- package/frontend/src/lib/components/VersionHistory.svelte +96 -0
- package/frontend/src/lib/components/editor/DocumentTitle.svelte +87 -0
- package/frontend/src/lib/components/editor/EditorToolbar.svelte +1367 -0
- package/frontend/src/lib/components/editor/HiAiEditor.svelte +531 -0
- package/frontend/src/lib/components/editor/LinkDialog.svelte +134 -0
- package/frontend/src/lib/components/editor/MarkdownToggle.svelte +88 -0
- package/frontend/src/lib/components/editor/editorExtensions.ts +53 -0
- package/frontend/src/lib/components/editor/markdown.ts +38 -0
- package/frontend/src/lib/components/sidebar/FolderTree.svelte +731 -0
- package/frontend/src/lib/components/sidebar/RecentDocs.svelte +311 -0
- package/frontend/src/lib/components/sidebar/Sidebar.svelte +156 -0
- package/frontend/src/lib/components/sidebar/TagList.svelte +200 -0
- package/frontend/src/lib/components/ui/confirm-dialog/ConfirmDialog.svelte +76 -0
- package/frontend/src/lib/components/ui/confirm-dialog/index.ts +1 -0
- package/frontend/src/lib/stores/tag-store.svelte.ts +56 -0
- package/frontend/src/lib/stores/theme.svelte.ts +97 -0
- package/frontend/src/lib/svelte.d.ts +6 -0
- package/frontend/src/lib/types.ts +44 -0
- package/frontend/src/lib/utils/clipboard.ts +17 -0
- package/frontend/src/lib/utils/strip-markdown.ts +59 -0
- package/frontend/src/lib/utils.ts +33 -0
- package/frontend/src/routes/(app)/+layout.svelte +17 -0
- package/frontend/src/routes/(app)/+page.server.ts +10 -0
- package/frontend/src/routes/(app)/+page.svelte +303 -0
- package/frontend/src/routes/(app)/docs/[id]/+page.server.ts +10 -0
- package/frontend/src/routes/(app)/docs/[id]/+page.svelte +1108 -0
- package/frontend/src/routes/(app)/docs/[id]/+page.ts +24 -0
- package/frontend/src/routes/(app)/search/+page.svelte +593 -0
- package/frontend/src/routes/(app)/search/+page.ts +25 -0
- package/frontend/src/routes/+error.svelte +12 -0
- package/frontend/src/routes/+layout.svelte +18 -0
- package/frontend/src/routes/+layout.ts +2 -0
- package/frontend/src/routes/api/[...path]/+server.ts +111 -0
- package/frontend/src/routes/folders/[id]/+page.server.ts +10 -0
- package/frontend/src/routes/folders/[id]/+page.svelte +319 -0
- package/frontend/src/routes/folders/[id]/+page.ts +14 -0
- package/frontend/src/routes/login/+page.svelte +90 -0
- package/frontend/src/routes/register/+page.svelte +97 -0
- package/frontend/src/routes/s/[token]/+page.svelte +496 -0
- package/frontend/src/routes/s/[token]/+page.ts +5 -0
- package/frontend/src/routes/settings/+page.svelte +175 -0
- package/frontend/static/favicon.png +0 -0
- package/frontend/static/logo.png +0 -0
- package/frontend/svelte.config.js +15 -0
- package/frontend/tsconfig.json +15 -0
- package/frontend/vite.config.ts +25 -0
- package/init.sql +9 -0
- package/logo.png +0 -0
- package/package.json +39 -0
- package/package.public.json +39 -0
- package/packages/db/drizzle.config.ts +10 -0
- package/packages/db/package.json +30 -0
- package/packages/db/src/client.ts +9 -0
- package/packages/db/src/index.ts +2 -0
- package/packages/db/src/migrations/0000_nice_bedlam.sql +165 -0
- package/packages/db/src/migrations/0001_w2_3_test.sql +5 -0
- package/packages/db/src/migrations/0002_rename_content_json.sql +2 -0
- package/packages/db/src/migrations/meta/0000_snapshot.json +1331 -0
- package/packages/db/src/migrations/meta/0001_snapshot.json +1399 -0
- package/packages/db/src/migrations/meta/0002_snapshot.json +1399 -0
- package/packages/db/src/migrations/meta/_journal.json +27 -0
- package/packages/db/src/schema.ts +378 -0
- package/packages/db/tsconfig.json +17 -0
- package/scripts/export-openapi.ts +37 -0
- package/scripts/health-check.sh +75 -0
- package/scripts/migrate.sh +135 -0
- package/scripts/prework_backup.sh +25 -0
- package/scripts/release.sh +83 -0
- package/tsconfig.json +25 -0
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Check, Copy, FileText, Folder, Lock } from "lucide-svelte";
|
|
3
|
+
import { marked } from "marked";
|
|
4
|
+
import { page } from "$app/state";
|
|
5
|
+
import * as m from "$lib/paraglide/messages.js";
|
|
6
|
+
|
|
7
|
+
const token = $derived(page.params.token);
|
|
8
|
+
|
|
9
|
+
let password = $state("");
|
|
10
|
+
let requiresPassword = $state(false);
|
|
11
|
+
let error = $state("");
|
|
12
|
+
let loading = $state(false);
|
|
13
|
+
let shareData = $state<{
|
|
14
|
+
type?: string;
|
|
15
|
+
data?: {
|
|
16
|
+
title?: string;
|
|
17
|
+
content?: string;
|
|
18
|
+
contentJson?: object | null;
|
|
19
|
+
name?: string;
|
|
20
|
+
documents?: { title: string }[];
|
|
21
|
+
};
|
|
22
|
+
} | null>(null);
|
|
23
|
+
let copied = $state(false);
|
|
24
|
+
let copiedText = $state(false);
|
|
25
|
+
|
|
26
|
+
// SvelteKit-injected fetch avoids the `window.fetch` warning during SSR/CSR
|
|
27
|
+
// transitions. Falls back to global fetch when running outside a load fn
|
|
28
|
+
// (e.g. in unit tests).
|
|
29
|
+
const kitFetch = $derived(
|
|
30
|
+
(page.data.fetch as typeof fetch | undefined) ?? globalThis.fetch,
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
// Configure marked for safe, GFM-flavored rendering of shared document
|
|
34
|
+
// markdown. The HiAiEditor JSON path (contentJson) is preferred when the
|
|
35
|
+
// server provides it, but `content` (raw markdown) is the universal fallback.
|
|
36
|
+
marked.setOptions({ gfm: true, breaks: false });
|
|
37
|
+
|
|
38
|
+
function renderContent(): string {
|
|
39
|
+
if (!shareData?.data) return "";
|
|
40
|
+
const docJson = shareData.data.contentJson as
|
|
41
|
+
| { content?: unknown }
|
|
42
|
+
| null
|
|
43
|
+
| undefined;
|
|
44
|
+
if (docJson && Array.isArray(docJson.content)) {
|
|
45
|
+
return docToHtml(docJson as ProseMirrorDoc);
|
|
46
|
+
}
|
|
47
|
+
const md = shareData.data.content;
|
|
48
|
+
if (md && md.length > 0) {
|
|
49
|
+
return marked.parse(md, { async: false }) as string;
|
|
50
|
+
}
|
|
51
|
+
return "";
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
type ProseMirrorNode = {
|
|
55
|
+
type: string;
|
|
56
|
+
text?: string;
|
|
57
|
+
content?: ProseMirrorNode[];
|
|
58
|
+
attrs?: Record<string, unknown>;
|
|
59
|
+
marks?: Array<{ type: string; attrs?: Record<string, unknown> }>;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
type ProseMirrorDoc = ProseMirrorNode & { content?: ProseMirrorNode[] };
|
|
63
|
+
|
|
64
|
+
function escapeHtml(s: string): string {
|
|
65
|
+
return s
|
|
66
|
+
.replace(/&/g, "&")
|
|
67
|
+
.replace(/</g, "<")
|
|
68
|
+
.replace(/>/g, ">")
|
|
69
|
+
.replace(/"/g, """)
|
|
70
|
+
.replace(/'/g, "'");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function docToHtml(doc: ProseMirrorDoc): string {
|
|
74
|
+
const renderNode = (node: ProseMirrorNode): string => {
|
|
75
|
+
if (node.type === "text") {
|
|
76
|
+
let html = escapeHtml(node.text ?? "");
|
|
77
|
+
for (const mark of node.marks ?? []) {
|
|
78
|
+
html = wrapMark(mark, html);
|
|
79
|
+
}
|
|
80
|
+
return html;
|
|
81
|
+
}
|
|
82
|
+
const inner = (node.content ?? []).map(renderNode).join("");
|
|
83
|
+
return wrapBlock(node, inner);
|
|
84
|
+
};
|
|
85
|
+
return (doc.content ?? []).map(renderNode).join("");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function wrapMark(
|
|
89
|
+
mark: { type: string; attrs?: Record<string, unknown> },
|
|
90
|
+
html: string,
|
|
91
|
+
): string {
|
|
92
|
+
switch (mark.type) {
|
|
93
|
+
case "bold":
|
|
94
|
+
return `<strong>${html}</strong>`;
|
|
95
|
+
case "italic":
|
|
96
|
+
return `<em>${html}</em>`;
|
|
97
|
+
case "strike":
|
|
98
|
+
case "strikethrough":
|
|
99
|
+
return `<s>${html}</s>`;
|
|
100
|
+
case "underline":
|
|
101
|
+
return `<u>${html}</u>`;
|
|
102
|
+
case "code":
|
|
103
|
+
return `<code>${html}</code>`;
|
|
104
|
+
case "link": {
|
|
105
|
+
const href = (mark.attrs?.href as string) ?? "#";
|
|
106
|
+
return `<a href="${escapeHtml(href)}" target="_blank" rel="noopener noreferrer">${html}</a>`;
|
|
107
|
+
}
|
|
108
|
+
case "highlight": {
|
|
109
|
+
const color = (mark.attrs?.color as string) ?? "#fde68a";
|
|
110
|
+
return `<mark style="background-color: ${escapeHtml(color)}">${html}</mark>`;
|
|
111
|
+
}
|
|
112
|
+
default:
|
|
113
|
+
return html;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Returns an inline `style` attribute fragment (` style="text-align: X"`) for
|
|
118
|
+
// the block-level textAlign attribute, or an empty string when alignment is
|
|
119
|
+
// unset / not a recognized value. Whitelists the four values supported by the
|
|
120
|
+
// editor to avoid passing attacker-controlled strings into the markup.
|
|
121
|
+
function alignStyle(attrs?: Record<string, unknown>): string {
|
|
122
|
+
const align = attrs?.textAlign as string | undefined;
|
|
123
|
+
if (
|
|
124
|
+
align !== "left" &&
|
|
125
|
+
align !== "center" &&
|
|
126
|
+
align !== "right" &&
|
|
127
|
+
align !== "justify"
|
|
128
|
+
) {
|
|
129
|
+
return "";
|
|
130
|
+
}
|
|
131
|
+
return ` style="text-align: ${align}"`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function wrapBlock(node: ProseMirrorNode, inner: string): string {
|
|
135
|
+
const lang = (node.attrs?.language as string) ?? "";
|
|
136
|
+
const align = alignStyle(node.attrs);
|
|
137
|
+
switch (node.type) {
|
|
138
|
+
case "paragraph":
|
|
139
|
+
return `<p${align}>${inner}</p>`;
|
|
140
|
+
case "heading": {
|
|
141
|
+
const level = Math.min(Math.max(Number(node.attrs?.level ?? 1), 1), 6);
|
|
142
|
+
return `<h${level}${align}>${inner}</h${level}>`;
|
|
143
|
+
}
|
|
144
|
+
case "bulletList":
|
|
145
|
+
return `<ul${align}>${inner}</ul>`;
|
|
146
|
+
case "orderedList":
|
|
147
|
+
return `<ol${align}>${inner}</ol>`;
|
|
148
|
+
case "listItem":
|
|
149
|
+
return `<li${align}>${inner}</li>`;
|
|
150
|
+
case "taskList":
|
|
151
|
+
return `<ul data-type="taskList">${inner}</ul>`;
|
|
152
|
+
case "taskItem": {
|
|
153
|
+
// Read-only checkbox reflecting the saved checked state.
|
|
154
|
+
const isChecked =
|
|
155
|
+
node.attrs?.checked === true || node.attrs?.checked === "true";
|
|
156
|
+
const checked = isChecked ? " checked" : "";
|
|
157
|
+
return `<li data-type="taskItem"${isChecked ? ' data-checked="true"' : ""}><label><input type="checkbox" onclick="return false;" class="cursor-default" ${checked} /></label><div>${inner}</div></li>`;
|
|
158
|
+
}
|
|
159
|
+
case "blockquote":
|
|
160
|
+
return `<blockquote${align}>${inner}</blockquote>`;
|
|
161
|
+
case "table":
|
|
162
|
+
return `<table><tbody>${inner}</tbody></table>`;
|
|
163
|
+
case "tableRow":
|
|
164
|
+
return `<tr>${inner}</tr>`;
|
|
165
|
+
case "tableHeader":
|
|
166
|
+
return `<th${align}>${inner}</th>`;
|
|
167
|
+
case "tableCell":
|
|
168
|
+
return `<td${align}>${inner}</td>`;
|
|
169
|
+
case "codeBlock":
|
|
170
|
+
return `<pre><code${lang ? ` class="language-${escapeHtml(lang)}"` : ""}>${inner}</code></pre>`;
|
|
171
|
+
case "horizontalRule":
|
|
172
|
+
return `<hr />`;
|
|
173
|
+
case "hardBreak":
|
|
174
|
+
return `<br />`;
|
|
175
|
+
case "image": {
|
|
176
|
+
const src = (node.attrs?.src as string) ?? "";
|
|
177
|
+
const alt = (node.attrs?.alt as string) ?? "";
|
|
178
|
+
return `<img src="${escapeHtml(src)}" alt="${escapeHtml(alt)}" />`;
|
|
179
|
+
}
|
|
180
|
+
default:
|
|
181
|
+
return inner;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const renderedContent = $derived(renderContent());
|
|
186
|
+
|
|
187
|
+
async function fetchShare() {
|
|
188
|
+
loading = true;
|
|
189
|
+
try {
|
|
190
|
+
const headers: Record<string, string> = {};
|
|
191
|
+
if (password) headers["x-share-password"] = password;
|
|
192
|
+
|
|
193
|
+
const res = await kitFetch(`/api/share/${token}`, { headers });
|
|
194
|
+
const data = await res.json();
|
|
195
|
+
|
|
196
|
+
if (res.status === 401) {
|
|
197
|
+
// Server signals the share is password-protected when `requiresPassword`
|
|
198
|
+
// is true on the response (initial load with no password supplied).
|
|
199
|
+
// A wrong password comes back as 401 without that flag — we must keep
|
|
200
|
+
// the password form visible and surface a retryable inline error
|
|
201
|
+
// instead of replacing the whole view with a fatal error banner.
|
|
202
|
+
requiresPassword = true;
|
|
203
|
+
password = "";
|
|
204
|
+
error = data.requiresPassword ? "" : m.share_password_incorrect();
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (!res.ok) {
|
|
208
|
+
// Non-password failure (expired, missing share, server error). Drop
|
|
209
|
+
// the password form so the user sees the banner with a way home.
|
|
210
|
+
requiresPassword = false;
|
|
211
|
+
password = "";
|
|
212
|
+
error = data.error ?? m.share_load_error();
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
// Success — clear transient auth state so the document view renders.
|
|
216
|
+
shareData = data;
|
|
217
|
+
requiresPassword = false;
|
|
218
|
+
password = "";
|
|
219
|
+
error = "";
|
|
220
|
+
} catch (_e) {
|
|
221
|
+
requiresPassword = false;
|
|
222
|
+
password = "";
|
|
223
|
+
error = m.share_network_error();
|
|
224
|
+
} finally {
|
|
225
|
+
loading = false;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function copyUrl() {
|
|
230
|
+
navigator.clipboard.writeText(window.location.href);
|
|
231
|
+
copied = true;
|
|
232
|
+
setTimeout(() => {
|
|
233
|
+
copied = false;
|
|
234
|
+
}, 2000);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function copyText() {
|
|
238
|
+
const text = shareData?.data?.content ?? "";
|
|
239
|
+
if (!text) return;
|
|
240
|
+
navigator.clipboard.writeText(text);
|
|
241
|
+
copiedText = true;
|
|
242
|
+
setTimeout(() => {
|
|
243
|
+
copiedText = false;
|
|
244
|
+
}, 2000);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
fetchShare();
|
|
248
|
+
</script>
|
|
249
|
+
|
|
250
|
+
<svelte:head>
|
|
251
|
+
<title>{m.share_page_title()}</title>
|
|
252
|
+
</svelte:head>
|
|
253
|
+
|
|
254
|
+
<div class="flex min-h-screen items-center justify-center bg-background p-4">
|
|
255
|
+
{#if loading}
|
|
256
|
+
<div class="text-muted-foreground">{m.action_loading()}</div>
|
|
257
|
+
{:else if shareData}
|
|
258
|
+
<div class="w-full max-w-3xl space-y-6">
|
|
259
|
+
<div class="flex items-center justify-between">
|
|
260
|
+
<div class="flex items-center gap-2 text-sm text-muted-foreground">
|
|
261
|
+
{#if shareData.type === "document"}
|
|
262
|
+
<FileText class="h-4 w-4" />
|
|
263
|
+
{:else}
|
|
264
|
+
<Folder class="h-4 w-4" />
|
|
265
|
+
{/if}
|
|
266
|
+
{m.share_via_label()}
|
|
267
|
+
</div>
|
|
268
|
+
<div class="flex items-center gap-2">
|
|
269
|
+
{#if shareData.type === "document" && shareData.data?.content}
|
|
270
|
+
<button
|
|
271
|
+
onclick={copyText}
|
|
272
|
+
class="flex items-center gap-1 rounded-md border border-border px-3 py-1.5 text-xs hover:bg-accent"
|
|
273
|
+
>
|
|
274
|
+
{#if copiedText}
|
|
275
|
+
<Check class="h-3 w-3" /> {m.share_copied()}
|
|
276
|
+
{:else}
|
|
277
|
+
<Copy class="h-3 w-3" /> Copy Text
|
|
278
|
+
{/if}
|
|
279
|
+
</button>
|
|
280
|
+
{/if}
|
|
281
|
+
<button
|
|
282
|
+
onclick={copyUrl}
|
|
283
|
+
class="flex items-center gap-1 rounded-md border border-border px-3 py-1.5 text-xs hover:bg-accent"
|
|
284
|
+
>
|
|
285
|
+
{#if copied}
|
|
286
|
+
<Check class="h-3 w-3" /> {m.share_copied()}
|
|
287
|
+
{:else}
|
|
288
|
+
<Copy class="h-3 w-3" /> {m.share_copy_link()}
|
|
289
|
+
{/if}
|
|
290
|
+
</button>
|
|
291
|
+
</div>
|
|
292
|
+
</div>
|
|
293
|
+
|
|
294
|
+
{#if shareData.type === "document"}
|
|
295
|
+
<article class="rounded-lg border border-border bg-card p-8 shadow-sm">
|
|
296
|
+
<h1 class="mb-6 text-3xl font-bold tracking-tight">{shareData.data?.title ?? ""}</h1>
|
|
297
|
+
{#if renderedContent}
|
|
298
|
+
<div class="shared-doc-body">
|
|
299
|
+
{@html renderedContent}
|
|
300
|
+
</div>
|
|
301
|
+
{:else}
|
|
302
|
+
<p class="text-muted-foreground">{m.share_empty_document()}</p>
|
|
303
|
+
{/if}
|
|
304
|
+
</article>
|
|
305
|
+
{:else}
|
|
306
|
+
<div class="rounded-lg border border-border bg-card p-6 shadow-sm">
|
|
307
|
+
<h1 class="mb-4 text-2xl font-bold">{shareData.data?.name ?? ""}</h1>
|
|
308
|
+
{#if shareData.data?.documents && shareData.data.documents.length > 0}
|
|
309
|
+
<ul class="space-y-2">
|
|
310
|
+
{#each shareData.data.documents as doc}
|
|
311
|
+
<li class="flex items-center gap-2 rounded-md border border-border px-3 py-2">
|
|
312
|
+
<FileText class="h-4 w-4 text-muted-foreground" />
|
|
313
|
+
<span>{doc.title}</span>
|
|
314
|
+
</li>
|
|
315
|
+
{/each}
|
|
316
|
+
</ul>
|
|
317
|
+
{:else}
|
|
318
|
+
<p class="text-muted-foreground">{m.share_folder_empty()}</p>
|
|
319
|
+
{/if}
|
|
320
|
+
</div>
|
|
321
|
+
{/if}
|
|
322
|
+
</div>
|
|
323
|
+
{:else if requiresPassword}
|
|
324
|
+
<form
|
|
325
|
+
onsubmit={(e) => { e.preventDefault(); fetchShare(); }}
|
|
326
|
+
class="w-full max-w-sm space-y-4 rounded-lg border border-border bg-card p-6 shadow-sm"
|
|
327
|
+
>
|
|
328
|
+
<div class="flex items-center gap-2 text-lg font-semibold">
|
|
329
|
+
<Lock class="h-5 w-5" />
|
|
330
|
+
{m.share_password_required()}
|
|
331
|
+
</div>
|
|
332
|
+
{#if error}
|
|
333
|
+
<div
|
|
334
|
+
role="alert"
|
|
335
|
+
class="rounded-md border border-destructive/50 bg-destructive/10 px-3 py-2 text-sm text-destructive"
|
|
336
|
+
>
|
|
337
|
+
{error}
|
|
338
|
+
</div>
|
|
339
|
+
{/if}
|
|
340
|
+
<input
|
|
341
|
+
type="password"
|
|
342
|
+
bind:value={password}
|
|
343
|
+
placeholder={m.share_password_placeholder()}
|
|
344
|
+
class="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
|
345
|
+
/>
|
|
346
|
+
<button
|
|
347
|
+
type="submit"
|
|
348
|
+
class="inline-flex h-9 w-full items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground shadow hover:bg-primary/90"
|
|
349
|
+
>
|
|
350
|
+
{m.share_access_button()}
|
|
351
|
+
</button>
|
|
352
|
+
</form>
|
|
353
|
+
{:else if error}
|
|
354
|
+
<div class="w-full max-w-md rounded-lg border border-destructive/50 bg-destructive/10 p-6 text-center">
|
|
355
|
+
<p class="text-lg font-medium text-destructive">{error}</p>
|
|
356
|
+
<a href="/" class="mt-4 inline-block text-sm text-primary underline">{m.share_go_home()}</a>
|
|
357
|
+
</div>
|
|
358
|
+
{/if}
|
|
359
|
+
</div>
|
|
360
|
+
|
|
361
|
+
<style>
|
|
362
|
+
/* Shared document body — minimal styles for the markdown/JSON HTML output
|
|
363
|
+
rendered via {@html}. The `prose` class from @tailwindcss/typography
|
|
364
|
+
is not installed in this project, so we provide the essentials. */
|
|
365
|
+
.shared-doc-body {
|
|
366
|
+
color: var(--foreground);
|
|
367
|
+
line-height: 1.7;
|
|
368
|
+
font-size: 1rem;
|
|
369
|
+
word-wrap: break-word;
|
|
370
|
+
}
|
|
371
|
+
.shared-doc-body :global(h1) {
|
|
372
|
+
font-size: 1.875rem;
|
|
373
|
+
font-weight: 700;
|
|
374
|
+
margin: 1.5rem 0 0.75rem;
|
|
375
|
+
letter-spacing: -0.02em;
|
|
376
|
+
}
|
|
377
|
+
.shared-doc-body :global(h2) {
|
|
378
|
+
font-size: 1.5rem;
|
|
379
|
+
font-weight: 700;
|
|
380
|
+
margin: 1.25rem 0 0.5rem;
|
|
381
|
+
}
|
|
382
|
+
.shared-doc-body :global(h3) {
|
|
383
|
+
font-size: 1.25rem;
|
|
384
|
+
font-weight: 600;
|
|
385
|
+
margin: 1rem 0 0.5rem;
|
|
386
|
+
}
|
|
387
|
+
.shared-doc-body :global(p) {
|
|
388
|
+
margin: 0.5rem 0;
|
|
389
|
+
}
|
|
390
|
+
.shared-doc-body :global(ul),
|
|
391
|
+
.shared-doc-body :global(ol) {
|
|
392
|
+
padding-left: 1.5rem;
|
|
393
|
+
margin: 0.5rem 0;
|
|
394
|
+
}
|
|
395
|
+
.shared-doc-body :global(ul) {
|
|
396
|
+
list-style-type: disc;
|
|
397
|
+
}
|
|
398
|
+
.shared-doc-body :global(ol) {
|
|
399
|
+
list-style-type: decimal;
|
|
400
|
+
}
|
|
401
|
+
.shared-doc-body :global(li) {
|
|
402
|
+
margin: 0.25rem 0;
|
|
403
|
+
}
|
|
404
|
+
/* Task lists — no bullet, checkbox + content laid out in a row. */
|
|
405
|
+
.shared-doc-body :global(ul[data-type="taskList"]) {
|
|
406
|
+
list-style: none;
|
|
407
|
+
padding-left: 0.25rem;
|
|
408
|
+
}
|
|
409
|
+
.shared-doc-body :global(ul[data-type="taskList"] li) {
|
|
410
|
+
display: flex;
|
|
411
|
+
align-items: flex-start;
|
|
412
|
+
gap: 0.5rem;
|
|
413
|
+
}
|
|
414
|
+
.shared-doc-body :global(ul[data-type="taskList"] li > label) {
|
|
415
|
+
display: flex;
|
|
416
|
+
align-items: center;
|
|
417
|
+
justify-content: center;
|
|
418
|
+
height: 1.7em;
|
|
419
|
+
margin: 0;
|
|
420
|
+
}
|
|
421
|
+
.shared-doc-body :global(ul[data-type="taskList"] li > div > p) {
|
|
422
|
+
margin: 0;
|
|
423
|
+
}
|
|
424
|
+
.shared-doc-body :global(ul[data-type="taskList"] input[type="checkbox"]) {
|
|
425
|
+
accent-color: var(--primary);
|
|
426
|
+
}
|
|
427
|
+
.shared-doc-body :global(blockquote) {
|
|
428
|
+
border-left: 3px solid var(--border);
|
|
429
|
+
padding-left: 1rem;
|
|
430
|
+
margin: 0.75rem 0;
|
|
431
|
+
color: var(--muted-foreground);
|
|
432
|
+
font-style: italic;
|
|
433
|
+
}
|
|
434
|
+
.shared-doc-body :global(code) {
|
|
435
|
+
background: var(--muted);
|
|
436
|
+
padding: 0.125rem 0.375rem;
|
|
437
|
+
border-radius: 4px;
|
|
438
|
+
font-size: 0.875em;
|
|
439
|
+
font-family: "Fira Code", "Consolas", monospace;
|
|
440
|
+
}
|
|
441
|
+
.shared-doc-body :global(pre) {
|
|
442
|
+
background: var(--muted);
|
|
443
|
+
color: var(--foreground);
|
|
444
|
+
border: 1px solid var(--border);
|
|
445
|
+
padding: 1rem;
|
|
446
|
+
border-radius: 8px;
|
|
447
|
+
font-family: "Fira Code", "Consolas", monospace;
|
|
448
|
+
font-size: 0.875rem;
|
|
449
|
+
line-height: 1.6;
|
|
450
|
+
overflow-x: auto;
|
|
451
|
+
margin: 0.75rem 0;
|
|
452
|
+
}
|
|
453
|
+
.shared-doc-body :global(pre code) {
|
|
454
|
+
background: transparent;
|
|
455
|
+
padding: 0;
|
|
456
|
+
font-size: inherit;
|
|
457
|
+
color: inherit;
|
|
458
|
+
}
|
|
459
|
+
.shared-doc-body :global(hr) {
|
|
460
|
+
border: none;
|
|
461
|
+
border-top: 1px solid var(--border);
|
|
462
|
+
margin: 1.5rem 0;
|
|
463
|
+
}
|
|
464
|
+
.shared-doc-body :global(a) {
|
|
465
|
+
color: var(--primary);
|
|
466
|
+
text-decoration: underline;
|
|
467
|
+
}
|
|
468
|
+
.shared-doc-body :global(img) {
|
|
469
|
+
max-width: 100%;
|
|
470
|
+
height: auto;
|
|
471
|
+
border-radius: 6px;
|
|
472
|
+
margin: 0.75rem 0;
|
|
473
|
+
display: block;
|
|
474
|
+
}
|
|
475
|
+
.shared-doc-body :global(mark) {
|
|
476
|
+
background-color: var(--highlight-default, #fde68a);
|
|
477
|
+
border-radius: 2px;
|
|
478
|
+
padding: 0 2px;
|
|
479
|
+
}
|
|
480
|
+
.shared-doc-body :global(table) {
|
|
481
|
+
border-collapse: collapse;
|
|
482
|
+
width: 100%;
|
|
483
|
+
margin: 0.75rem 0;
|
|
484
|
+
}
|
|
485
|
+
.shared-doc-body :global(th),
|
|
486
|
+
.shared-doc-body :global(td) {
|
|
487
|
+
border: 1px solid var(--border);
|
|
488
|
+
padding: 0.4rem 0.6rem;
|
|
489
|
+
text-align: left;
|
|
490
|
+
vertical-align: top;
|
|
491
|
+
}
|
|
492
|
+
.shared-doc-body :global(th) {
|
|
493
|
+
background: var(--muted);
|
|
494
|
+
font-weight: 600;
|
|
495
|
+
}
|
|
496
|
+
</style>
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount } from "svelte";
|
|
3
|
+
import { goto } from "$app/navigation";
|
|
4
|
+
import {
|
|
5
|
+
deleteAccount,
|
|
6
|
+
getEmbeddingConfig,
|
|
7
|
+
getProfile,
|
|
8
|
+
updateEmbeddingConfig,
|
|
9
|
+
updateProfile,
|
|
10
|
+
} from "$lib/api/settings";
|
|
11
|
+
import { signOut } from "$lib/auth-client";
|
|
12
|
+
import * as m from "$lib/paraglide/messages.js";
|
|
13
|
+
import { themeStore } from "$lib/stores/theme.svelte";
|
|
14
|
+
|
|
15
|
+
let loggingOut = $state(false);
|
|
16
|
+
|
|
17
|
+
async function handleLogout() {
|
|
18
|
+
loggingOut = true;
|
|
19
|
+
try {
|
|
20
|
+
await signOut();
|
|
21
|
+
goto("/login");
|
|
22
|
+
} catch {
|
|
23
|
+
loggingOut = false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let activeTab = $state<"profile" | "embedding" | "danger">("profile");
|
|
28
|
+
let saveStatus = $state<"idle" | "saving" | "saved" | "error">("idle");
|
|
29
|
+
|
|
30
|
+
let name = $state("User");
|
|
31
|
+
let email = $state("user@example.com");
|
|
32
|
+
let embeddingProvider = $state("ollama");
|
|
33
|
+
let embeddingModel = $state("nomic-embed-text");
|
|
34
|
+
let deleteConfirm = $state(false);
|
|
35
|
+
|
|
36
|
+
onMount(async () => {
|
|
37
|
+
try {
|
|
38
|
+
const profile = await getProfile();
|
|
39
|
+
if (profile.name) name = profile.name;
|
|
40
|
+
if (profile.email) email = profile.email;
|
|
41
|
+
} catch {
|
|
42
|
+
// Use defaults
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const config = getEmbeddingConfig();
|
|
46
|
+
embeddingProvider = config.provider;
|
|
47
|
+
embeddingModel = config.model;
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
async function saveProfile() {
|
|
51
|
+
saveStatus = "saving";
|
|
52
|
+
try {
|
|
53
|
+
await updateProfile({ name });
|
|
54
|
+
saveStatus = "saved";
|
|
55
|
+
setTimeout(() => {
|
|
56
|
+
saveStatus = "idle";
|
|
57
|
+
}, 2000);
|
|
58
|
+
} catch {
|
|
59
|
+
saveStatus = "error";
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function saveEmbedding() {
|
|
64
|
+
saveStatus = "saving";
|
|
65
|
+
try {
|
|
66
|
+
updateEmbeddingConfig({
|
|
67
|
+
provider: embeddingProvider as "ollama" | "openrouter" | "voyage",
|
|
68
|
+
model: embeddingModel,
|
|
69
|
+
});
|
|
70
|
+
saveStatus = "saved";
|
|
71
|
+
setTimeout(() => {
|
|
72
|
+
saveStatus = "idle";
|
|
73
|
+
}, 2000);
|
|
74
|
+
} catch {
|
|
75
|
+
saveStatus = "error";
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function handleDeleteAccount() {
|
|
80
|
+
try {
|
|
81
|
+
await deleteAccount();
|
|
82
|
+
goto("/login");
|
|
83
|
+
} catch {
|
|
84
|
+
alert(m.settings_delete_failed());
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
</script>
|
|
88
|
+
|
|
89
|
+
<svelte:head>
|
|
90
|
+
<title>{m.settings_page_title()}</title>
|
|
91
|
+
</svelte:head>
|
|
92
|
+
|
|
93
|
+
<div class="mx-auto max-w-2xl p-6">
|
|
94
|
+
<h1 class="mb-6 text-2xl font-semibold">{m.settings_title()}</h1>
|
|
95
|
+
|
|
96
|
+
<div class="mb-6 flex gap-1 rounded-lg border border-border p-1">
|
|
97
|
+
{#each [["profile", m.settings_profile()], ["embedding", m.settings_tab_embedding()], ["danger", m.settings_tab_danger()]] as [key, label]}
|
|
98
|
+
<button
|
|
99
|
+
onclick={() => { activeTab = key as typeof activeTab; }}
|
|
100
|
+
class="flex-1 rounded-md px-3 py-1.5 text-sm font-medium transition-colors
|
|
101
|
+
{activeTab === key ? 'bg-primary text-primary-foreground' : 'text-muted-foreground hover:bg-accent hover:text-accent-foreground'}"
|
|
102
|
+
>
|
|
103
|
+
{label}
|
|
104
|
+
</button>
|
|
105
|
+
{/each}
|
|
106
|
+
</div>
|
|
107
|
+
|
|
108
|
+
{#if activeTab === "profile"}
|
|
109
|
+
<div class="space-y-4 rounded-lg border border-border bg-card p-6">
|
|
110
|
+
<h2 class="text-lg font-medium">{m.settings_profile()}</h2>
|
|
111
|
+
<div class="space-y-2">
|
|
112
|
+
<label for="name" class="text-sm font-medium">{m.settings_name()}</label>
|
|
113
|
+
<input id="name" bind:value={name} class="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring" />
|
|
114
|
+
</div>
|
|
115
|
+
<div class="space-y-2">
|
|
116
|
+
<label for="email" class="text-sm font-medium">{m.settings_email()}</label>
|
|
117
|
+
<input id="email" type="email" bind:value={email} class="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring" disabled />
|
|
118
|
+
</div>
|
|
119
|
+
<div class="flex items-center gap-3">
|
|
120
|
+
<button onclick={saveProfile} disabled={saveStatus === "saving"} class="rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50">
|
|
121
|
+
{saveStatus === "saving" ? m.settings_saving() : saveStatus === "saved" ? m.settings_saved_status() : m.settings_save()}
|
|
122
|
+
</button>
|
|
123
|
+
<button
|
|
124
|
+
id="logout-button"
|
|
125
|
+
onclick={handleLogout}
|
|
126
|
+
disabled={loggingOut}
|
|
127
|
+
class="rounded-md border border-border px-4 py-2 text-sm font-medium text-muted-foreground transition-colors hover:bg-destructive hover:text-destructive-foreground disabled:opacity-50"
|
|
128
|
+
>
|
|
129
|
+
{loggingOut ? "…" : m.auth_logout()}
|
|
130
|
+
</button>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
{/if}
|
|
134
|
+
|
|
135
|
+
{#if activeTab === "embedding"}
|
|
136
|
+
<div class="space-y-4 rounded-lg border border-border bg-card p-6">
|
|
137
|
+
<h2 class="text-lg font-medium">{m.settings_embedding_title()}</h2>
|
|
138
|
+
<div class="space-y-2">
|
|
139
|
+
<label for="provider" class="text-sm font-medium">{m.settings_embedding_provider()}</label>
|
|
140
|
+
<select id="provider" bind:value={embeddingProvider} class="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring">
|
|
141
|
+
<option value="ollama">{m.settings_embedding_provider_ollama()}</option>
|
|
142
|
+
<option value="openrouter">{m.settings_embedding_provider_openrouter()}</option>
|
|
143
|
+
<option value="voyage">{m.settings_embedding_provider_voyage()}</option>
|
|
144
|
+
</select>
|
|
145
|
+
</div>
|
|
146
|
+
<div class="space-y-2">
|
|
147
|
+
<label for="model" class="text-sm font-medium">{m.settings_embedding_model()}</label>
|
|
148
|
+
<input id="model" bind:value={embeddingModel} placeholder="nomic-embed-text" class="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring" />
|
|
149
|
+
</div>
|
|
150
|
+
<button onclick={saveEmbedding} disabled={saveStatus === "saving"} class="rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50">
|
|
151
|
+
{saveStatus === "saving" ? m.settings_saving() : saveStatus === "saved" ? m.settings_saved_status() : m.settings_embedding_save()}
|
|
152
|
+
</button>
|
|
153
|
+
</div>
|
|
154
|
+
{/if}
|
|
155
|
+
|
|
156
|
+
{#if activeTab === "danger"}
|
|
157
|
+
<div class="space-y-4 rounded-lg border border-destructive/50 bg-card p-6">
|
|
158
|
+
<h2 class="text-lg font-medium text-destructive">{m.settings_danger_title()}</h2>
|
|
159
|
+
<p class="text-sm text-muted-foreground">{m.settings_danger_description()}</p>
|
|
160
|
+
{#if !deleteConfirm}
|
|
161
|
+
<button onclick={() => { deleteConfirm = true; }} class="rounded-md bg-destructive px-4 py-2 text-sm font-medium text-destructive-foreground hover:bg-destructive/90">{m.settings_delete_account()}</button>
|
|
162
|
+
{:else}
|
|
163
|
+
<div class="flex items-center gap-3">
|
|
164
|
+
<span class="text-sm font-medium">{m.settings_delete_confirm_text()}</span>
|
|
165
|
+
<button onclick={handleDeleteAccount} class="rounded-md bg-destructive px-3 py-1.5 text-sm font-medium text-destructive-foreground hover:bg-destructive/90">{m.settings_delete_confirm_yes()}</button>
|
|
166
|
+
<button onclick={() => { deleteConfirm = false; }} class="rounded-md border border-border px-3 py-1.5 text-sm font-medium hover:bg-accent">{m.action_cancel()}</button>
|
|
167
|
+
</div>
|
|
168
|
+
{/if}
|
|
169
|
+
</div>
|
|
170
|
+
{/if}
|
|
171
|
+
|
|
172
|
+
<p class="mt-4 text-xs text-muted-foreground">
|
|
173
|
+
{m.settings_theme()}: {themeStore.value} ({themeStore.isDark ? "dark" : "light"})
|
|
174
|
+
</p>
|
|
175
|
+
</div>
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import adapter from "@sveltejs/adapter-node";
|
|
2
|
+
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
|
|
3
|
+
|
|
4
|
+
/** @type {import('@sveltejs/kit').Config} */
|
|
5
|
+
const config = {
|
|
6
|
+
preprocess: [vitePreprocess()],
|
|
7
|
+
kit: {
|
|
8
|
+
adapter: adapter(),
|
|
9
|
+
alias: {
|
|
10
|
+
"$lib/*": "src/lib/*",
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default config;
|