@cryptiklemur/lattice 1.3.0 → 1.4.0
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/bun.lock +705 -2
- package/client/index.html +1 -13
- package/client/package.json +6 -1
- package/client/src/App.tsx +2 -0
- package/client/src/commands.ts +36 -0
- package/client/src/components/chat/AttachmentChips.tsx +116 -0
- package/client/src/components/chat/ChatInput.tsx +250 -73
- package/client/src/components/chat/ChatView.tsx +242 -10
- package/client/src/components/chat/CommandPalette.tsx +162 -0
- package/client/src/components/chat/Message.tsx +23 -2
- package/client/src/components/chat/PromptQuestion.tsx +271 -0
- package/client/src/components/chat/TodoCard.tsx +57 -0
- package/client/src/components/chat/ToolResultRenderer.tsx +2 -1
- package/client/src/components/chat/VoiceRecorder.tsx +85 -0
- package/client/src/components/project-settings/ProjectMemory.tsx +12 -2
- package/client/src/components/project-settings/ProjectNotifications.tsx +48 -0
- package/client/src/components/project-settings/ProjectRules.tsx +10 -1
- package/client/src/components/project-settings/ProjectSettingsView.tsx +6 -0
- package/client/src/components/settings/Appearance.tsx +1 -0
- package/client/src/components/settings/ClaudeSettings.tsx +10 -0
- package/client/src/components/settings/Editor.tsx +123 -0
- package/client/src/components/settings/GlobalMcp.tsx +10 -1
- package/client/src/components/settings/GlobalMemory.tsx +19 -0
- package/client/src/components/settings/GlobalRules.tsx +149 -0
- package/client/src/components/settings/GlobalSkills.tsx +10 -0
- package/client/src/components/settings/Notifications.tsx +88 -0
- package/client/src/components/settings/SettingsView.tsx +12 -0
- package/client/src/components/settings/skill-shared.tsx +2 -1
- package/client/src/components/setup/SetupWizard.tsx +1 -1
- package/client/src/components/sidebar/NodeSettingsModal.tsx +23 -1
- package/client/src/components/sidebar/ProjectDropdown.tsx +176 -27
- package/client/src/components/sidebar/SettingsSidebar.tsx +11 -1
- package/client/src/components/sidebar/Sidebar.tsx +35 -2
- package/client/src/components/sidebar/UserIsland.tsx +18 -7
- package/client/src/components/ui/UpdatePrompt.tsx +47 -0
- package/client/src/components/workspace/FileBrowser.tsx +174 -0
- package/client/src/components/workspace/FileTree.tsx +129 -0
- package/client/src/components/workspace/FileViewer.tsx +211 -0
- package/client/src/components/workspace/NoteCard.tsx +119 -0
- package/client/src/components/workspace/NotesView.tsx +102 -0
- package/client/src/components/workspace/ScheduledTasksView.tsx +117 -0
- package/client/src/components/workspace/SplitPane.tsx +81 -0
- package/client/src/components/workspace/TabBar.tsx +185 -0
- package/client/src/components/workspace/TaskCard.tsx +158 -0
- package/client/src/components/workspace/TaskEditModal.tsx +114 -0
- package/client/src/components/{panels/Terminal.tsx → workspace/TerminalInstance.tsx} +50 -7
- package/client/src/components/workspace/TerminalView.tsx +110 -0
- package/client/src/components/workspace/WorkspaceView.tsx +116 -0
- package/client/src/hooks/useAttachments.ts +280 -0
- package/client/src/hooks/useEditorConfig.ts +28 -0
- package/client/src/hooks/useIdleDetection.ts +44 -0
- package/client/src/hooks/useInstallPrompt.ts +53 -0
- package/client/src/hooks/useNotifications.ts +54 -0
- package/client/src/hooks/useOnline.ts +6 -0
- package/client/src/hooks/useSession.ts +110 -4
- package/client/src/hooks/useSpinnerVerb.ts +36 -0
- package/client/src/hooks/useSwipeDrawer.ts +275 -0
- package/client/src/hooks/useVoiceRecorder.ts +123 -0
- package/client/src/hooks/useWorkspace.ts +48 -0
- package/client/src/providers/WebSocketProvider.tsx +18 -0
- package/client/src/router.tsx +48 -20
- package/client/src/stores/session.ts +136 -0
- package/client/src/stores/sidebar.ts +3 -2
- package/client/src/stores/workspace.ts +254 -0
- package/client/src/styles/global.css +123 -0
- package/client/src/utils/editorUrl.ts +62 -0
- package/client/vite.config.ts +53 -1
- package/package.json +1 -1
- package/server/src/daemon.ts +11 -1
- package/server/src/features/scheduler.ts +23 -0
- package/server/src/features/sticky-notes.ts +5 -3
- package/server/src/handlers/attachment.ts +172 -0
- package/server/src/handlers/chat.ts +43 -2
- package/server/src/handlers/editor.ts +40 -0
- package/server/src/handlers/fs.ts +10 -2
- package/server/src/handlers/memory.ts +3 -0
- package/server/src/handlers/notes.ts +4 -2
- package/server/src/handlers/scheduler.ts +18 -1
- package/server/src/handlers/session.ts +14 -8
- package/server/src/handlers/settings.ts +37 -2
- package/server/src/handlers/terminal.ts +13 -6
- package/server/src/project/pty-worker.cjs +83 -0
- package/server/src/project/sdk-bridge.ts +266 -11
- package/server/src/project/terminal.ts +78 -34
- package/shared/src/messages.ts +145 -4
- package/shared/src/models.ts +27 -1
- package/shared/src/project-settings.ts +1 -1
- package/tp.js +19 -0
- package/client/public/manifest.json +0 -24
- package/client/public/sw.js +0 -61
- package/client/src/components/panels/FileBrowser.tsx +0 -241
- package/client/src/components/panels/StickyNotes.tsx +0 -187
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from "react";
|
|
2
|
+
import { useWebSocket } from "../../hooks/useWebSocket";
|
|
3
|
+
import { useSaveState } from "../../hooks/useSaveState";
|
|
4
|
+
import { SaveFooter } from "../ui/SaveFooter";
|
|
5
|
+
import type { ServerMessage, SettingsDataMessage, SettingsUpdateMessage } from "@lattice/shared";
|
|
6
|
+
|
|
7
|
+
var IDE_OPTIONS = [
|
|
8
|
+
{ id: "vscode", label: "VS Code" },
|
|
9
|
+
{ id: "vscode-insiders", label: "VS Code Insiders" },
|
|
10
|
+
{ id: "cursor", label: "Cursor" },
|
|
11
|
+
{ id: "webstorm", label: "WebStorm" },
|
|
12
|
+
{ id: "intellij", label: "IntelliJ IDEA" },
|
|
13
|
+
{ id: "pycharm", label: "PyCharm" },
|
|
14
|
+
{ id: "goland", label: "GoLand" },
|
|
15
|
+
{ id: "notepad++", label: "Notepad++" },
|
|
16
|
+
{ id: "sublime", label: "Sublime Text" },
|
|
17
|
+
{ id: "custom", label: "Custom" },
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
export function Editor() {
|
|
21
|
+
var { send, subscribe, unsubscribe } = useWebSocket();
|
|
22
|
+
var [ideType, setIdeType] = useState("vscode");
|
|
23
|
+
var [customCommand, setCustomCommand] = useState("");
|
|
24
|
+
var save = useSaveState();
|
|
25
|
+
var saveRef = useRef(save);
|
|
26
|
+
saveRef.current = save;
|
|
27
|
+
|
|
28
|
+
useEffect(function () {
|
|
29
|
+
function handleMessage(msg: ServerMessage) {
|
|
30
|
+
if (msg.type !== "settings:data") return;
|
|
31
|
+
var data = msg as SettingsDataMessage;
|
|
32
|
+
var cfg = data.config as unknown as Record<string, unknown>;
|
|
33
|
+
var editor = cfg.editor as { type?: string; customCommand?: string } | undefined;
|
|
34
|
+
|
|
35
|
+
var newType = editor?.type ?? "vscode";
|
|
36
|
+
var newCustomCommand = editor?.customCommand ?? "";
|
|
37
|
+
|
|
38
|
+
if (saveRef.current.saving) {
|
|
39
|
+
saveRef.current.confirmSave();
|
|
40
|
+
} else {
|
|
41
|
+
setIdeType(newType);
|
|
42
|
+
setCustomCommand(newCustomCommand);
|
|
43
|
+
saveRef.current.resetFromServer();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
subscribe("settings:data", handleMessage);
|
|
48
|
+
send({ type: "settings:get" });
|
|
49
|
+
|
|
50
|
+
return function () {
|
|
51
|
+
unsubscribe("settings:data", handleMessage);
|
|
52
|
+
};
|
|
53
|
+
}, []);
|
|
54
|
+
|
|
55
|
+
function handleTypeChange(value: string) {
|
|
56
|
+
setIdeType(value);
|
|
57
|
+
save.markDirty();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function handleCustomCommandChange(value: string) {
|
|
61
|
+
setCustomCommand(value);
|
|
62
|
+
save.markDirty();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function handleSave() {
|
|
66
|
+
save.startSave();
|
|
67
|
+
var editorSettings: { type: string; customCommand?: string } = { type: ideType };
|
|
68
|
+
if (ideType === "custom") {
|
|
69
|
+
editorSettings.customCommand = customCommand;
|
|
70
|
+
}
|
|
71
|
+
var updateMsg: SettingsUpdateMessage = {
|
|
72
|
+
type: "settings:update",
|
|
73
|
+
settings: { editor: editorSettings } as SettingsUpdateMessage["settings"],
|
|
74
|
+
};
|
|
75
|
+
send(updateMsg);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<div className="py-2">
|
|
80
|
+
<div className="mb-5">
|
|
81
|
+
<label htmlFor="editor-ide-type" className="block text-[12px] font-semibold text-base-content/40 mb-1">IDE</label>
|
|
82
|
+
<p className="text-[11px] text-base-content/30 mb-2">Choose your preferred editor for opening files from Lattice.</p>
|
|
83
|
+
<p className="text-[11px] text-base-content/20 mb-2">Files open via your editor's URL scheme handler.</p>
|
|
84
|
+
<select
|
|
85
|
+
id="editor-ide-type"
|
|
86
|
+
value={ideType}
|
|
87
|
+
onChange={function (e) { handleTypeChange(e.target.value); }}
|
|
88
|
+
className="w-full h-9 px-3 bg-base-300 border border-base-content/15 rounded-xl text-base-content text-[13px] focus:border-primary focus-visible:outline-none transition-colors duration-[120ms]"
|
|
89
|
+
>
|
|
90
|
+
{IDE_OPTIONS.map(function (opt) {
|
|
91
|
+
return (
|
|
92
|
+
<option key={opt.id} value={opt.id} className="bg-base-300">
|
|
93
|
+
{opt.label}
|
|
94
|
+
</option>
|
|
95
|
+
);
|
|
96
|
+
})}
|
|
97
|
+
</select>
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
{ideType === "custom" && (
|
|
101
|
+
<div className="mb-5">
|
|
102
|
+
<label htmlFor="editor-custom-command" className="block text-[12px] font-semibold text-base-content/40 mb-1">Shell command</label>
|
|
103
|
+
<p className="text-[11px] text-base-content/30 mb-2">Use {"{file}"} for the file path and {"{line}"} for the line number.</p>
|
|
104
|
+
<input
|
|
105
|
+
id="editor-custom-command"
|
|
106
|
+
type="text"
|
|
107
|
+
value={customCommand}
|
|
108
|
+
onChange={function (e) { handleCustomCommandChange(e.target.value); }}
|
|
109
|
+
placeholder="vim +{line} {file}"
|
|
110
|
+
className="w-full h-9 px-3 bg-base-300 border border-base-content/15 rounded-xl text-base-content font-mono text-[13px] focus:border-primary focus-visible:outline-none transition-colors duration-[120ms]"
|
|
111
|
+
/>
|
|
112
|
+
</div>
|
|
113
|
+
)}
|
|
114
|
+
|
|
115
|
+
<SaveFooter
|
|
116
|
+
dirty={save.dirty}
|
|
117
|
+
saving={save.saving}
|
|
118
|
+
saveState={save.saveState}
|
|
119
|
+
onSave={handleSave}
|
|
120
|
+
/>
|
|
121
|
+
</div>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useState, useEffect } from "react";
|
|
2
|
-
import { Plus, Pencil, Trash2 } from "lucide-react";
|
|
2
|
+
import { Plus, Pencil, Trash2, ExternalLink } from "lucide-react";
|
|
3
3
|
import { useSaveState } from "../../hooks/useSaveState";
|
|
4
4
|
import { SaveFooter } from "../ui/SaveFooter";
|
|
5
5
|
import { useWebSocket } from "../../hooks/useWebSocket";
|
|
@@ -110,6 +110,15 @@ export function GlobalMcp() {
|
|
|
110
110
|
|
|
111
111
|
return (
|
|
112
112
|
<div className="py-2">
|
|
113
|
+
<a
|
|
114
|
+
href="https://docs.anthropic.com/en/docs/claude-code/mcp-servers"
|
|
115
|
+
target="_blank"
|
|
116
|
+
rel="noopener noreferrer"
|
|
117
|
+
className="text-[11px] text-base-content/30 hover:text-primary/70 flex items-center gap-1 mb-4 transition-colors"
|
|
118
|
+
>
|
|
119
|
+
<ExternalLink size={11} />
|
|
120
|
+
Claude Code docs
|
|
121
|
+
</a>
|
|
113
122
|
{entries.length === 0 && !adding && (
|
|
114
123
|
<div className="py-4 text-center text-[13px] text-base-content/30 mb-3">
|
|
115
124
|
No global MCP servers configured.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ExternalLink } from "lucide-react";
|
|
2
|
+
import { ProjectMemory } from "../project-settings/ProjectMemory";
|
|
3
|
+
|
|
4
|
+
export function GlobalMemory() {
|
|
5
|
+
return (
|
|
6
|
+
<div>
|
|
7
|
+
<a
|
|
8
|
+
href="https://docs.anthropic.com/en/docs/claude-code/memory"
|
|
9
|
+
target="_blank"
|
|
10
|
+
rel="noopener noreferrer"
|
|
11
|
+
className="text-[11px] text-base-content/30 hover:text-primary/70 flex items-center gap-1 mb-4 transition-colors"
|
|
12
|
+
>
|
|
13
|
+
<ExternalLink size={11} />
|
|
14
|
+
Claude Code docs
|
|
15
|
+
</a>
|
|
16
|
+
<ProjectMemory projectSlug="__global__" />
|
|
17
|
+
</div>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { useState, useEffect } from "react";
|
|
2
|
+
import { ChevronDown, ChevronRight, ExternalLink } from "lucide-react";
|
|
3
|
+
import { useWebSocket } from "../../hooks/useWebSocket";
|
|
4
|
+
import type { ServerMessage, SettingsDataMessage } from "@lattice/shared";
|
|
5
|
+
|
|
6
|
+
interface RuleEntry {
|
|
7
|
+
filename: string;
|
|
8
|
+
content: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function GlobalRules() {
|
|
12
|
+
var { send, subscribe, unsubscribe } = useWebSocket();
|
|
13
|
+
var [rules, setRules] = useState<RuleEntry[]>([]);
|
|
14
|
+
var [claudeMd, setClaudeMd] = useState("");
|
|
15
|
+
var [expanded, setExpanded] = useState<Set<number>>(new Set());
|
|
16
|
+
var [claudeMdExpanded, setClaudeMdExpanded] = useState(false);
|
|
17
|
+
|
|
18
|
+
useEffect(function () {
|
|
19
|
+
function handleMessage(msg: ServerMessage) {
|
|
20
|
+
if (msg.type !== "settings:data") return;
|
|
21
|
+
var data = msg as SettingsDataMessage;
|
|
22
|
+
var cfg = data.config as unknown as Record<string, unknown>;
|
|
23
|
+
setClaudeMd(cfg.claudeMd ? String(cfg.claudeMd) : "");
|
|
24
|
+
setRules(data.globalRules ?? []);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
subscribe("settings:data", handleMessage);
|
|
28
|
+
send({ type: "settings:get" });
|
|
29
|
+
|
|
30
|
+
return function () {
|
|
31
|
+
unsubscribe("settings:data", handleMessage);
|
|
32
|
+
};
|
|
33
|
+
}, []);
|
|
34
|
+
|
|
35
|
+
function toggle(idx: number) {
|
|
36
|
+
setExpanded(function (prev) {
|
|
37
|
+
var next = new Set(prev);
|
|
38
|
+
if (next.has(idx)) {
|
|
39
|
+
next.delete(idx);
|
|
40
|
+
} else {
|
|
41
|
+
next.add(idx);
|
|
42
|
+
}
|
|
43
|
+
return next;
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function preview(content: string): string {
|
|
48
|
+
var trimmed = content.trim();
|
|
49
|
+
if (trimmed.length <= 80) return trimmed;
|
|
50
|
+
return trimmed.slice(0, 80) + "...";
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<div className="py-2">
|
|
55
|
+
<a
|
|
56
|
+
href="https://docs.anthropic.com/en/docs/claude-code/memory"
|
|
57
|
+
target="_blank"
|
|
58
|
+
rel="noopener noreferrer"
|
|
59
|
+
className="text-[11px] text-base-content/30 hover:text-primary/70 flex items-center gap-1 mb-4 transition-colors"
|
|
60
|
+
>
|
|
61
|
+
<ExternalLink size={11} />
|
|
62
|
+
Claude Code docs
|
|
63
|
+
</a>
|
|
64
|
+
<div className="mb-6">
|
|
65
|
+
<h2 className="text-[12px] font-semibold text-base-content/40 mb-3">
|
|
66
|
+
Global CLAUDE.md
|
|
67
|
+
</h2>
|
|
68
|
+
{!claudeMd && (
|
|
69
|
+
<div className="py-4 text-center text-[13px] text-base-content/30">
|
|
70
|
+
No global CLAUDE.md found.
|
|
71
|
+
</div>
|
|
72
|
+
)}
|
|
73
|
+
{claudeMd && (
|
|
74
|
+
<div className="border border-base-content/10 rounded-xl overflow-hidden">
|
|
75
|
+
<button
|
|
76
|
+
onClick={function () { setClaudeMdExpanded(!claudeMdExpanded); }}
|
|
77
|
+
className="w-full flex items-center gap-2 px-3 py-2 bg-base-300/50 hover:bg-base-300/70 transition-colors duration-[120ms] cursor-pointer text-left"
|
|
78
|
+
>
|
|
79
|
+
{claudeMdExpanded
|
|
80
|
+
? <ChevronDown size={12} className="text-base-content/40 flex-shrink-0" />
|
|
81
|
+
: <ChevronRight size={12} className="text-base-content/40 flex-shrink-0" />
|
|
82
|
+
}
|
|
83
|
+
<span className="font-mono text-[12px] text-base-content/40 flex-1 truncate">
|
|
84
|
+
~/.claude/CLAUDE.md
|
|
85
|
+
</span>
|
|
86
|
+
</button>
|
|
87
|
+
{!claudeMdExpanded && (
|
|
88
|
+
<div className="px-3 py-1.5 text-[11px] text-base-content/30 font-mono truncate">
|
|
89
|
+
{preview(claudeMd)}
|
|
90
|
+
</div>
|
|
91
|
+
)}
|
|
92
|
+
{claudeMdExpanded && (
|
|
93
|
+
<pre className="px-3 py-2 text-[12px] text-base-content/40 font-mono whitespace-pre-wrap break-words bg-base-300/30 max-h-[300px] overflow-auto">
|
|
94
|
+
{claudeMd}
|
|
95
|
+
</pre>
|
|
96
|
+
)}
|
|
97
|
+
</div>
|
|
98
|
+
)}
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
<div>
|
|
102
|
+
<h2 className="text-[12px] font-semibold text-base-content/40 mb-3">
|
|
103
|
+
Global Rules
|
|
104
|
+
</h2>
|
|
105
|
+
<p className="text-[11px] text-base-content/30 mb-3">
|
|
106
|
+
Rules from ~/.claude/rules/ — read-only.
|
|
107
|
+
</p>
|
|
108
|
+
{rules.length === 0 && (
|
|
109
|
+
<div className="py-4 text-center text-[13px] text-base-content/30">
|
|
110
|
+
No global rules found.
|
|
111
|
+
</div>
|
|
112
|
+
)}
|
|
113
|
+
{rules.length > 0 && (
|
|
114
|
+
<div className="flex flex-col gap-1.5">
|
|
115
|
+
{rules.map(function (rule, idx) {
|
|
116
|
+
var isExpanded = expanded.has(idx);
|
|
117
|
+
return (
|
|
118
|
+
<div key={rule.filename + "-" + idx} className="border border-base-content/10 rounded-xl overflow-hidden">
|
|
119
|
+
<button
|
|
120
|
+
onClick={function () { toggle(idx); }}
|
|
121
|
+
className="w-full flex items-center gap-2 px-3 py-2 bg-base-300/50 hover:bg-base-300/70 transition-colors duration-[120ms] cursor-pointer text-left"
|
|
122
|
+
>
|
|
123
|
+
{isExpanded
|
|
124
|
+
? <ChevronDown size={12} className="text-base-content/40 flex-shrink-0" />
|
|
125
|
+
: <ChevronRight size={12} className="text-base-content/40 flex-shrink-0" />
|
|
126
|
+
}
|
|
127
|
+
<span className="font-mono text-[12px] text-base-content/40 flex-1 truncate">
|
|
128
|
+
{rule.filename}
|
|
129
|
+
</span>
|
|
130
|
+
</button>
|
|
131
|
+
{!isExpanded && (
|
|
132
|
+
<div className="px-3 py-1.5 text-[11px] text-base-content/30 font-mono truncate">
|
|
133
|
+
{preview(rule.content)}
|
|
134
|
+
</div>
|
|
135
|
+
)}
|
|
136
|
+
{isExpanded && (
|
|
137
|
+
<pre className="px-3 py-2 text-[12px] text-base-content/40 font-mono whitespace-pre-wrap break-words bg-base-300/30 max-h-[300px] overflow-auto">
|
|
138
|
+
{rule.content}
|
|
139
|
+
</pre>
|
|
140
|
+
)}
|
|
141
|
+
</div>
|
|
142
|
+
);
|
|
143
|
+
})}
|
|
144
|
+
</div>
|
|
145
|
+
)}
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useState, useEffect } from "react";
|
|
2
|
+
import { ExternalLink } from "lucide-react";
|
|
2
3
|
import { useWebSocket } from "../../hooks/useWebSocket";
|
|
3
4
|
import { SkillMarketplace } from "./SkillMarketplace";
|
|
4
5
|
import { SkillItem, SkillActions, SkillViewModal } from "./skill-shared";
|
|
@@ -81,6 +82,15 @@ export function GlobalSkills() {
|
|
|
81
82
|
|
|
82
83
|
return (
|
|
83
84
|
<div className="py-2 space-y-6">
|
|
85
|
+
<a
|
|
86
|
+
href="https://docs.anthropic.com/en/docs/claude-code/slash-commands"
|
|
87
|
+
target="_blank"
|
|
88
|
+
rel="noopener noreferrer"
|
|
89
|
+
className="text-[11px] text-base-content/30 hover:text-primary/70 flex items-center gap-1 mb-4 transition-colors"
|
|
90
|
+
>
|
|
91
|
+
<ExternalLink size={11} />
|
|
92
|
+
Claude Code docs
|
|
93
|
+
</a>
|
|
84
94
|
<div>
|
|
85
95
|
<div className="text-[12px] font-semibold text-base-content/40 mb-2">Installed Skills</div>
|
|
86
96
|
{skills.length === 0 ? (
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { useNotificationPreference } from "../../hooks/useNotifications";
|
|
3
|
+
|
|
4
|
+
var NOTIFICATION_TYPES = [
|
|
5
|
+
{ key: "responses", label: "Claude responses", description: "When Claude finishes a response" },
|
|
6
|
+
{ key: "mesh", label: "Mesh node changes", description: "When nodes come online or go offline" },
|
|
7
|
+
{ key: "connection", label: "Connection events", description: "WebSocket connect and disconnect" },
|
|
8
|
+
] as const;
|
|
9
|
+
|
|
10
|
+
function loadTypePrefs(): Record<string, boolean> {
|
|
11
|
+
try {
|
|
12
|
+
var stored = localStorage.getItem("lattice-notification-types");
|
|
13
|
+
if (stored) return JSON.parse(stored);
|
|
14
|
+
} catch {}
|
|
15
|
+
return { responses: true, mesh: true, connection: true };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function saveTypePrefs(prefs: Record<string, boolean>): void {
|
|
19
|
+
localStorage.setItem("lattice-notification-types", JSON.stringify(prefs));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function Notifications() {
|
|
23
|
+
var notifPref = useNotificationPreference();
|
|
24
|
+
var [typePrefs, setTypePrefs] = useState<Record<string, boolean>>(loadTypePrefs);
|
|
25
|
+
|
|
26
|
+
function toggleType(key: string) {
|
|
27
|
+
setTypePrefs(function (prev) {
|
|
28
|
+
var next = { ...prev, [key]: !prev[key] };
|
|
29
|
+
saveTypePrefs(next);
|
|
30
|
+
return next;
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<div className="py-2 space-y-6">
|
|
36
|
+
<div>
|
|
37
|
+
<h2 className="text-[12px] font-semibold text-base-content/40 mb-3">
|
|
38
|
+
Browser Notifications
|
|
39
|
+
</h2>
|
|
40
|
+
<div className="flex items-center justify-between py-2 px-3 bg-base-300 border border-base-content/15 rounded-xl">
|
|
41
|
+
<div>
|
|
42
|
+
<div className="text-[13px] text-base-content">Enable notifications</div>
|
|
43
|
+
<div className="text-[11px] text-base-content/30 mt-0.5">
|
|
44
|
+
{notifPref.permission === "denied"
|
|
45
|
+
? "Blocked by browser — update in browser settings"
|
|
46
|
+
: "Get notified about events in Lattice"}
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
<input
|
|
50
|
+
type="checkbox"
|
|
51
|
+
className="toggle toggle-sm toggle-primary"
|
|
52
|
+
checked={notifPref.enabled}
|
|
53
|
+
onChange={notifPref.toggle}
|
|
54
|
+
disabled={notifPref.permission === "denied"}
|
|
55
|
+
/>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
<div>
|
|
60
|
+
<h2 className="text-[12px] font-semibold text-base-content/40 mb-3">
|
|
61
|
+
Notification Types
|
|
62
|
+
</h2>
|
|
63
|
+
<div className="flex flex-col gap-2">
|
|
64
|
+
{NOTIFICATION_TYPES.map(function (nt) {
|
|
65
|
+
return (
|
|
66
|
+
<div
|
|
67
|
+
key={nt.key}
|
|
68
|
+
className="flex items-center justify-between py-2 px-3 bg-base-300 border border-base-content/15 rounded-xl"
|
|
69
|
+
>
|
|
70
|
+
<div>
|
|
71
|
+
<div className="text-[13px] text-base-content">{nt.label}</div>
|
|
72
|
+
<div className="text-[11px] text-base-content/30 mt-0.5">{nt.description}</div>
|
|
73
|
+
</div>
|
|
74
|
+
<input
|
|
75
|
+
type="checkbox"
|
|
76
|
+
className="toggle toggle-sm toggle-primary"
|
|
77
|
+
checked={typePrefs[nt.key] !== false}
|
|
78
|
+
onChange={function () { toggleType(nt.key); }}
|
|
79
|
+
disabled={!notifPref.enabled}
|
|
80
|
+
/>
|
|
81
|
+
</div>
|
|
82
|
+
);
|
|
83
|
+
})}
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
@@ -6,24 +6,36 @@ import { Environment } from "./Environment";
|
|
|
6
6
|
import { MeshStatus } from "./MeshStatus";
|
|
7
7
|
import { GlobalMcp } from "./GlobalMcp";
|
|
8
8
|
import { GlobalSkills } from "./GlobalSkills";
|
|
9
|
+
import { Editor } from "./Editor";
|
|
10
|
+
import { GlobalRules } from "./GlobalRules";
|
|
11
|
+
import { GlobalMemory } from "./GlobalMemory";
|
|
12
|
+
import { Notifications } from "./Notifications";
|
|
9
13
|
import type { SettingsSection } from "../../stores/sidebar";
|
|
10
14
|
|
|
11
15
|
var SECTION_CONFIG: Record<string, { title: string }> = {
|
|
12
16
|
appearance: { title: "Appearance" },
|
|
17
|
+
notifications: { title: "Notifications" },
|
|
13
18
|
claude: { title: "Claude Settings" },
|
|
14
19
|
environment: { title: "Environment" },
|
|
15
20
|
mcp: { title: "MCP Servers" },
|
|
16
21
|
skills: { title: "Skills" },
|
|
17
22
|
nodes: { title: "Mesh Nodes" },
|
|
23
|
+
editor: { title: "Editor" },
|
|
24
|
+
rules: { title: "Rules" },
|
|
25
|
+
memory: { title: "Memory" },
|
|
18
26
|
};
|
|
19
27
|
|
|
20
28
|
function renderSection(section: SettingsSection) {
|
|
21
29
|
if (section === "appearance") return <Appearance />;
|
|
30
|
+
if (section === "notifications") return <Notifications />;
|
|
22
31
|
if (section === "claude") return <ClaudeSettings />;
|
|
23
32
|
if (section === "environment") return <Environment />;
|
|
24
33
|
if (section === "mcp") return <GlobalMcp />;
|
|
25
34
|
if (section === "skills") return <GlobalSkills />;
|
|
26
35
|
if (section === "nodes") return <MeshStatus />;
|
|
36
|
+
if (section === "editor") return <Editor />;
|
|
37
|
+
if (section === "rules") return <GlobalRules />;
|
|
38
|
+
if (section === "memory") return <GlobalMemory />;
|
|
27
39
|
return null;
|
|
28
40
|
}
|
|
29
41
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useState } from "react";
|
|
2
2
|
import { Trash2, RefreshCw, X, Loader2, FileText } from "lucide-react";
|
|
3
3
|
import Markdown from "react-markdown";
|
|
4
|
+
import remarkGfm from "remark-gfm";
|
|
4
5
|
import type { SkillInfo } from "@lattice/shared";
|
|
5
6
|
|
|
6
7
|
function parseFrontmatter(content: string): { meta: Record<string, string>; body: string } {
|
|
@@ -163,7 +164,7 @@ export function SkillViewModal({ path, content, onClose }: { path: string; conte
|
|
|
163
164
|
|
|
164
165
|
<div className="px-5 py-4">
|
|
165
166
|
<div className="prose prose-sm max-w-none prose-headings:text-base-content prose-headings:font-mono prose-p:text-base-content/70 prose-strong:text-base-content prose-code:text-base-content/60 prose-code:bg-base-100/50 prose-code:px-1 prose-code:py-0.5 prose-code:rounded prose-code:text-[11px] prose-pre:bg-base-100 prose-pre:text-base-content/70 prose-pre:text-[11px] prose-a:text-primary prose-li:text-base-content/70 prose-li:marker:text-base-content/30 [&>*:first-child]:mt-0 [&>*:last-child]:mb-0">
|
|
166
|
-
<Markdown>{parsed.body}</Markdown>
|
|
167
|
+
<Markdown remarkPlugins={[remarkGfm]}>{parsed.body}</Markdown>
|
|
167
168
|
</div>
|
|
168
169
|
</div>
|
|
169
170
|
</div>
|
|
@@ -25,6 +25,7 @@ export function NodeSettingsModal({ isOpen, onClose }: NodeSettingsModalProps) {
|
|
|
25
25
|
var [tls, setTls] = useState(false);
|
|
26
26
|
var [debug, setDebug] = useState(false);
|
|
27
27
|
var [copied, setCopied] = useState(false);
|
|
28
|
+
var [wsl, setWsl] = useState<boolean | "auto">("auto");
|
|
28
29
|
var copyTimeout = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
29
30
|
|
|
30
31
|
useEffect(function () {
|
|
@@ -43,6 +44,7 @@ export function NodeSettingsModal({ isOpen, onClose }: NodeSettingsModalProps) {
|
|
|
43
44
|
setPort(cfg.port);
|
|
44
45
|
setTls(cfg.tls);
|
|
45
46
|
setDebug(cfg.debug);
|
|
47
|
+
setWsl(cfg.wsl ?? "auto");
|
|
46
48
|
save.resetFromServer();
|
|
47
49
|
}
|
|
48
50
|
}
|
|
@@ -65,7 +67,7 @@ export function NodeSettingsModal({ isOpen, onClose }: NodeSettingsModalProps) {
|
|
|
65
67
|
save.startSave();
|
|
66
68
|
send({
|
|
67
69
|
type: "settings:update",
|
|
68
|
-
settings: { name, port, tls, debug },
|
|
70
|
+
settings: { name, port, tls, debug, wsl },
|
|
69
71
|
} as any);
|
|
70
72
|
}
|
|
71
73
|
|
|
@@ -169,6 +171,26 @@ export function NodeSettingsModal({ isOpen, onClose }: NodeSettingsModalProps) {
|
|
|
169
171
|
aria-label="Enable debug mode"
|
|
170
172
|
/>
|
|
171
173
|
</div>
|
|
174
|
+
|
|
175
|
+
<div>
|
|
176
|
+
<div className="text-[12px] font-semibold text-base-content/40 mb-1.5">WSL Mode</div>
|
|
177
|
+
<div className="text-[11px] text-base-content/30 mb-2">Convert file paths for Windows editors when running under WSL</div>
|
|
178
|
+
<select
|
|
179
|
+
value={String(wsl)}
|
|
180
|
+
onChange={function (e) {
|
|
181
|
+
var val = e.target.value;
|
|
182
|
+
if (val === "true") { setWsl(true); }
|
|
183
|
+
else if (val === "false") { setWsl(false); }
|
|
184
|
+
else { setWsl("auto"); }
|
|
185
|
+
save.markDirty();
|
|
186
|
+
}}
|
|
187
|
+
className={inputClass}
|
|
188
|
+
>
|
|
189
|
+
<option value="auto">Auto-detect</option>
|
|
190
|
+
<option value="true">Enabled</option>
|
|
191
|
+
<option value="false">Disabled</option>
|
|
192
|
+
</select>
|
|
193
|
+
</div>
|
|
172
194
|
</div>
|
|
173
195
|
|
|
174
196
|
<div className="px-5 py-3 border-t border-base-content/15">
|