@anchrd/intel-ui 0.12.0 → 0.14.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/package.json +3 -2
- package/src/agent/agent-calendar/agent-calendar.tsx +32 -14
- package/src/agent/agent-chat/agent-chat.tsx +3 -1
- package/src/agent/agent-cron/agent-cron.ts +49 -113
- package/src/agent/agent-definition/agent-definition.ts +18 -9
- package/src/agent/agent-entry-title/agent-entry-title.ts +3 -1
- package/src/agent/agent-log/agent-log.tsx +6 -3
- package/src/agent/agent-profile/agent-profile.tsx +37 -12
- package/src/agent/agent.tsx +17 -9
- package/src/app/app-sidebar/app-sidebar.tsx +2 -2
- package/src/app/app-tree/app-tree.tsx +5 -3
- package/src/app/app.tsx +2 -2
- package/src/app/header-search/header-search.tsx +3 -1
- package/src/app/settings-dialog/settings-dialog.tsx +115 -0
- package/src/app/tree-move/tree-move.tsx +3 -1
- package/src/app/user-footer/user-footer.tsx +25 -2
- package/src/app/view-toggle/view-toggle.tsx +2 -2
- package/src/archive/archive.tsx +3 -1
- package/src/components/ui/popover.tsx +41 -0
- package/src/entry-picker/entry-picker.tsx +3 -1
- package/src/flow-runs/flow-runs.tsx +6 -3
- package/src/flows/flows.tsx +13 -9
- package/src/flows/node-palette/node-palette.tsx +2 -2
- package/src/folder-contents/folder-contents.tsx +3 -1
- package/src/graph-pane/graph-pane.tsx +2 -2
- package/src/i18n/de.json +406 -0
- package/src/i18n/en.json +20 -5
- package/src/i18n/es.json +406 -0
- package/src/i18n/i18n-context.tsx +65 -0
- package/src/i18n/i18n-languages/i18n-languages.ts +83 -0
- package/src/i18n/i18n.ts +15 -11
- package/src/i18n/i18n.types.ts +43 -0
- package/src/main.tsx +33 -12
- package/src/modal/modal.tsx +2 -2
- package/src/node-table/node-table.tsx +3 -1
- package/src/nodes/nodes.tsx +7 -3
- package/src/resource-menu/resource-menu.tsx +8 -4
- package/src/router/router.types.ts +3 -2
- package/src/save-button/save-button.tsx +3 -3
- package/src/sign-in-refused/sign-in-refused.tsx +3 -2
- package/src/theme/theme-context.tsx +89 -0
- package/src/theme/theme.ts +51 -21
- package/src/timezone/timezone-combobox/timezone-combobox.tsx +149 -0
- package/src/timezone/timezone-context.tsx +45 -0
- package/src/timezone/timezone.ts +159 -0
- package/src/tools/tools.tsx +3 -1
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
SidebarMenuItem,
|
|
14
14
|
useSidebar,
|
|
15
15
|
} from "@/components/ui/sidebar";
|
|
16
|
-
import {
|
|
16
|
+
import { useI18n } from "@/i18n/i18n-context.tsx";
|
|
17
17
|
|
|
18
18
|
// One tree is the whole navigation: documents and flows side by side in the folder they share, with
|
|
19
19
|
// no Intelligence or Flows entry above them (ADR-0004). Tools stay below, next to the user, because
|
|
@@ -25,7 +25,7 @@ export function AppSidebar({
|
|
|
25
25
|
width: number;
|
|
26
26
|
onWidthChange(width: number): void;
|
|
27
27
|
}) {
|
|
28
|
-
const
|
|
28
|
+
const i18n = useI18n();
|
|
29
29
|
// `offcanvas` rather than `icon`: the icon rail is the mode for a list of equal entries with one
|
|
30
30
|
// icon each, and a tree says what it says through indentation, disclosure arrows and nesting —
|
|
31
31
|
// none of which survives 48px. Collapsing exists to free the canvas, so the sidebar goes.
|
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
import { flowEntry } from "@/data/intel-data-provider/intel-data-provider.ts";
|
|
41
41
|
import type { TreeEntry } from "@/data/intel-data-provider/intel-data-provider.types.ts";
|
|
42
42
|
import { useCapabilities } from "@/hooks/use-capabilities.ts";
|
|
43
|
+
import { useI18n } from "@/i18n/i18n-context.tsx";
|
|
43
44
|
import { kindIcons } from "@/kind-icon.ts";
|
|
44
45
|
import { Modal } from "@/modal/modal.tsx";
|
|
45
46
|
import { useIntelRouterContext } from "@/router/router-context.ts";
|
|
@@ -106,7 +107,8 @@ function levelKey(level: Level): readonly unknown[] {
|
|
|
106
107
|
export const TreeEntryMediaType = "application/x-intel-tree-entry";
|
|
107
108
|
|
|
108
109
|
export function AppTree() {
|
|
109
|
-
const { data
|
|
110
|
+
const { data } = useIntelRouterContext();
|
|
111
|
+
const i18n = useI18n();
|
|
110
112
|
const queryClient = useQueryClient();
|
|
111
113
|
const navigate = useNavigate();
|
|
112
114
|
const [expanded, setExpanded] = useState<readonly Level[]>([]);
|
|
@@ -720,7 +722,7 @@ function AddMenu({
|
|
|
720
722
|
variant?: "icon" | "row";
|
|
721
723
|
onSelect(kind: NewKind | "upload" | ImportKind): void;
|
|
722
724
|
}) {
|
|
723
|
-
const
|
|
725
|
+
const i18n = useI18n();
|
|
724
726
|
// Whether this deployment runs an agent runtime (#190). Until the answer arrives the item is
|
|
725
727
|
// absent rather than provisional: a menu entry that vanished after being seen was a lie, one that
|
|
726
728
|
// appears a beat late was merely loading.
|
|
@@ -789,7 +791,7 @@ function CreateForm({
|
|
|
789
791
|
pending: boolean;
|
|
790
792
|
submit(title: string, columns: string[]): void;
|
|
791
793
|
}) {
|
|
792
|
-
const
|
|
794
|
+
const i18n = useI18n();
|
|
793
795
|
const [title, setTitle] = useState("");
|
|
794
796
|
const [columns, setColumns] = useState("");
|
|
795
797
|
const parsedColumns = columnsOf(columns);
|
package/src/app/app.tsx
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
} from "@/components/ui/breadcrumb";
|
|
14
14
|
import { Separator } from "@/components/ui/separator";
|
|
15
15
|
import { SidebarInset, SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar";
|
|
16
|
-
import {
|
|
16
|
+
import { useI18n } from "@/i18n/i18n-context.tsx";
|
|
17
17
|
|
|
18
18
|
const sections = [
|
|
19
19
|
{ path: "/nodes", labelKey: "nav.nodes" },
|
|
@@ -22,7 +22,7 @@ const sections = [
|
|
|
22
22
|
] as const;
|
|
23
23
|
|
|
24
24
|
export function App() {
|
|
25
|
-
const
|
|
25
|
+
const i18n = useI18n();
|
|
26
26
|
const pathname = useRouterState({ select: (state) => state.location.pathname });
|
|
27
27
|
const current = sections.find(
|
|
28
28
|
(section) => pathname === section.path || pathname.startsWith(`${section.path}/`),
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
DialogHeader,
|
|
19
19
|
DialogTitle,
|
|
20
20
|
} from "@/components/ui/dialog";
|
|
21
|
+
import { useI18n } from "@/i18n/i18n-context.tsx";
|
|
21
22
|
import { useIntelRouterContext } from "@/router/router-context.ts";
|
|
22
23
|
|
|
23
24
|
const Kinds = ["node", "flow", "tool"] as const;
|
|
@@ -50,7 +51,8 @@ function matches(query: string, ...fields: (string | null)[]): boolean {
|
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
export function HeaderSearch() {
|
|
53
|
-
const { data
|
|
54
|
+
const { data } = useIntelRouterContext();
|
|
55
|
+
const i18n = useI18n();
|
|
54
56
|
const navigate = useNavigate();
|
|
55
57
|
const [open, setOpen] = useState(false);
|
|
56
58
|
const [text, setText] = useState("");
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Dialog,
|
|
3
|
+
DialogContent,
|
|
4
|
+
DialogDescription,
|
|
5
|
+
DialogHeader,
|
|
6
|
+
DialogTitle,
|
|
7
|
+
} from "@/components/ui/dialog";
|
|
8
|
+
import {
|
|
9
|
+
Select,
|
|
10
|
+
SelectContent,
|
|
11
|
+
SelectItem,
|
|
12
|
+
SelectTrigger,
|
|
13
|
+
SelectValue,
|
|
14
|
+
} from "@/components/ui/select";
|
|
15
|
+
import { useI18n, useLanguage } from "@/i18n/i18n-context.tsx";
|
|
16
|
+
import { languageName } from "@/i18n/i18n-languages/i18n-languages.ts";
|
|
17
|
+
import { type ThemeChoice, ThemeChoices } from "@/theme/theme.ts";
|
|
18
|
+
import { useTheme } from "@/theme/theme-context.tsx";
|
|
19
|
+
import { TimezoneCombobox } from "@/timezone/timezone-combobox/timezone-combobox.tsx";
|
|
20
|
+
import { useTimezoneSelection } from "@/timezone/timezone-context.tsx";
|
|
21
|
+
|
|
22
|
+
// The settings that belong to the person rather than to the installation: language, appearance and
|
|
23
|
+
// timezone. All three are client state — they live in localStorage, not in the Intel HTTP surface —
|
|
24
|
+
// so they do not belong on a screen with a save button. A choice applies at once; a dialog that
|
|
25
|
+
// previews and only commits on "Save" would carry three states too many for three dropdowns.
|
|
26
|
+
//
|
|
27
|
+
// The dialog is opened from outside (the user footer holds the state), because its trigger is a
|
|
28
|
+
// DropdownMenuItem: Radix unmounts the menu content when an item is chosen and would take a dialog
|
|
29
|
+
// nested inside it along in the same frame.
|
|
30
|
+
interface SettingsDialogProps {
|
|
31
|
+
open: boolean;
|
|
32
|
+
onOpenChange(open: boolean): void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function SettingsDialog({ open, onOpenChange }: SettingsDialogProps) {
|
|
36
|
+
const i18n = useI18n();
|
|
37
|
+
const language = useLanguage();
|
|
38
|
+
const theme = useTheme();
|
|
39
|
+
const timezone = useTimezoneSelection();
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
43
|
+
<DialogContent className="sm:max-w-md">
|
|
44
|
+
<DialogHeader>
|
|
45
|
+
<DialogTitle>{i18n.t("settings.title")}</DialogTitle>
|
|
46
|
+
<DialogDescription>{i18n.t("settings.description")}</DialogDescription>
|
|
47
|
+
</DialogHeader>
|
|
48
|
+
<div className="grid gap-6">
|
|
49
|
+
{/* Without a provider there is nothing to choose — then the block is absent rather than a
|
|
50
|
+
dead dropdown. The number of languages is deliberately NOT checked: there are always
|
|
51
|
+
at least the three built-in ones, so a condition on it would be dead code. */}
|
|
52
|
+
{language ? (
|
|
53
|
+
<div className="grid gap-2">
|
|
54
|
+
<label className="text-sm font-medium" htmlFor="settings-language">
|
|
55
|
+
{i18n.t("settings.language")}
|
|
56
|
+
</label>
|
|
57
|
+
<Select value={language.current} onValueChange={language.select}>
|
|
58
|
+
<SelectTrigger id="settings-language" className="w-full">
|
|
59
|
+
<SelectValue />
|
|
60
|
+
</SelectTrigger>
|
|
61
|
+
<SelectContent>
|
|
62
|
+
{language.codes.map((code) => (
|
|
63
|
+
<SelectItem key={code} value={code}>
|
|
64
|
+
{languageName(code)}
|
|
65
|
+
</SelectItem>
|
|
66
|
+
))}
|
|
67
|
+
</SelectContent>
|
|
68
|
+
</Select>
|
|
69
|
+
</div>
|
|
70
|
+
) : null}
|
|
71
|
+
{theme ? (
|
|
72
|
+
<div className="grid gap-2">
|
|
73
|
+
<label className="text-sm font-medium" htmlFor="settings-appearance">
|
|
74
|
+
{i18n.t("settings.appearance")}
|
|
75
|
+
</label>
|
|
76
|
+
{/* Radix hands out a raw string; the values come from the same list that fills the
|
|
77
|
+
menu, so the assertion here is the whole check. */}
|
|
78
|
+
<Select
|
|
79
|
+
value={theme.current}
|
|
80
|
+
onValueChange={(value) => theme.select(value as ThemeChoice)}
|
|
81
|
+
>
|
|
82
|
+
<SelectTrigger id="settings-appearance" className="w-full">
|
|
83
|
+
<SelectValue />
|
|
84
|
+
</SelectTrigger>
|
|
85
|
+
<SelectContent>
|
|
86
|
+
{ThemeChoices.map((choice) => (
|
|
87
|
+
<SelectItem key={choice} value={choice}>
|
|
88
|
+
{i18n.t(`settings.appearance.${choice}`)}
|
|
89
|
+
</SelectItem>
|
|
90
|
+
))}
|
|
91
|
+
</SelectContent>
|
|
92
|
+
</Select>
|
|
93
|
+
</div>
|
|
94
|
+
) : null}
|
|
95
|
+
{timezone ? (
|
|
96
|
+
<div className="grid gap-2">
|
|
97
|
+
<label className="text-sm font-medium" htmlFor="settings-timezone">
|
|
98
|
+
{i18n.t("settings.timezone")}
|
|
99
|
+
</label>
|
|
100
|
+
<TimezoneCombobox
|
|
101
|
+
id="settings-timezone"
|
|
102
|
+
value={timezone.current}
|
|
103
|
+
onChange={timezone.select}
|
|
104
|
+
describedBy="settings-timezone-hint"
|
|
105
|
+
/>
|
|
106
|
+
<p id="settings-timezone-hint" className="text-xs text-muted-foreground">
|
|
107
|
+
{i18n.t("settings.timezoneHint")}
|
|
108
|
+
</p>
|
|
109
|
+
</div>
|
|
110
|
+
) : null}
|
|
111
|
+
</div>
|
|
112
|
+
</DialogContent>
|
|
113
|
+
</Dialog>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
@@ -3,6 +3,7 @@ import { ChevronLeft, Folder } from "lucide-react";
|
|
|
3
3
|
import type * as React from "react";
|
|
4
4
|
import { useState } from "react";
|
|
5
5
|
import type { TreeEntry } from "@/data/intel-data-provider/intel-data-provider.types.ts";
|
|
6
|
+
import { useI18n } from "@/i18n/i18n-context.tsx";
|
|
6
7
|
import { Modal } from "@/modal/modal.tsx";
|
|
7
8
|
import { useIntelRouterContext } from "@/router/router-context.ts";
|
|
8
9
|
|
|
@@ -106,7 +107,8 @@ export function MoveDialog({
|
|
|
106
107
|
close(): void;
|
|
107
108
|
submit(destination: MoveDestination): void;
|
|
108
109
|
}) {
|
|
109
|
-
const { data
|
|
110
|
+
const { data } = useIntelRouterContext();
|
|
111
|
+
const i18n = useI18n();
|
|
110
112
|
const [destination, setDestination] = useState<MoveDestination | null>(initial);
|
|
111
113
|
// The browsed path, root first. It is also the descendant guard for the keyboard route: the row
|
|
112
114
|
// being moved is never offered, so its subtree can never be entered in the first place.
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { useMutation, useQuery } from "@tanstack/react-query";
|
|
2
2
|
import { useNavigate, useRouterState } from "@tanstack/react-router";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
Archive as ArchiveIcon,
|
|
5
|
+
ChevronsUpDown,
|
|
6
|
+
LogOut,
|
|
7
|
+
Settings,
|
|
8
|
+
User,
|
|
9
|
+
Wrench,
|
|
10
|
+
} from "lucide-react";
|
|
11
|
+
import { useState } from "react";
|
|
4
12
|
import {
|
|
5
13
|
DropdownMenu,
|
|
6
14
|
DropdownMenuContent,
|
|
@@ -10,7 +18,9 @@ import {
|
|
|
10
18
|
} from "@/components/ui/dropdown-menu";
|
|
11
19
|
import { SidebarMenu, SidebarMenuItem } from "@/components/ui/sidebar";
|
|
12
20
|
import { loginPath } from "@/data/intel-data-provider/intel-data-provider.ts";
|
|
21
|
+
import { useI18n } from "@/i18n/i18n-context.tsx";
|
|
13
22
|
import { useIntelRouterContext } from "@/router/router-context.ts";
|
|
23
|
+
import { SettingsDialog } from "../settings-dialog/settings-dialog.tsx";
|
|
14
24
|
|
|
15
25
|
/**
|
|
16
26
|
* Everything that belongs to the person rather than to the content, behind the person (#24).
|
|
@@ -24,9 +34,13 @@ import { useIntelRouterContext } from "@/router/router-context.ts";
|
|
|
24
34
|
* what it shows depends on who is asking.
|
|
25
35
|
*/
|
|
26
36
|
export function UserFooter() {
|
|
27
|
-
const { data
|
|
37
|
+
const { data } = useIntelRouterContext();
|
|
38
|
+
const i18n = useI18n();
|
|
28
39
|
const navigate = useNavigate();
|
|
29
40
|
const pathname = useRouterState({ select: (state) => state.location.pathname });
|
|
41
|
+
// The state lives here, not in the dialog: its trigger is a DropdownMenuItem, and Radix unmounts
|
|
42
|
+
// the menu content when an item is chosen — a dialog nested in there would go with it.
|
|
43
|
+
const [settingsOpen, setSettingsOpen] = useState(false);
|
|
30
44
|
const session = useQuery({ queryKey: ["session"], queryFn: () => data.getSession() });
|
|
31
45
|
const logout = useMutation({
|
|
32
46
|
mutationFn: () => data.logout(),
|
|
@@ -83,6 +97,13 @@ export function UserFooter() {
|
|
|
83
97
|
{i18n.t("archive.title")}
|
|
84
98
|
</DropdownMenuItem>
|
|
85
99
|
<DropdownMenuSeparator />
|
|
100
|
+
{/* Language, appearance and timezone belong to the person, like everything else behind
|
|
101
|
+
this menu — and unlike the two rows above, they change nothing anybody else sees. */}
|
|
102
|
+
<DropdownMenuItem onSelect={() => setSettingsOpen(true)}>
|
|
103
|
+
<Settings aria-hidden="true" />
|
|
104
|
+
{i18n.t("settings.title")}
|
|
105
|
+
</DropdownMenuItem>
|
|
106
|
+
<DropdownMenuSeparator />
|
|
86
107
|
<DropdownMenuItem
|
|
87
108
|
disabled={logout.isPending}
|
|
88
109
|
onSelect={() => logout.mutate()}
|
|
@@ -93,6 +114,8 @@ export function UserFooter() {
|
|
|
93
114
|
</DropdownMenuItem>
|
|
94
115
|
</DropdownMenuContent>
|
|
95
116
|
</DropdownMenu>
|
|
117
|
+
{/* ⚠️ Outside the DropdownMenu on purpose — see the state above. */}
|
|
118
|
+
<SettingsDialog open={settingsOpen} onOpenChange={setSettingsOpen} />
|
|
96
119
|
</SidebarMenuItem>
|
|
97
120
|
{/* ⚠️ Outside the menu on purpose. Choosing sign-out closes the popup, so a refusal rendered
|
|
98
121
|
inside it would be gone in the same frame it was written — the one message that must
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useNavigate, useRouterState } from "@tanstack/react-router";
|
|
2
2
|
import { History, Network, PencilRuler } from "lucide-react";
|
|
3
3
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
|
4
|
-
import {
|
|
4
|
+
import { useI18n } from "@/i18n/i18n-context.tsx";
|
|
5
5
|
import { type IntelView, viewFrom } from "@/router/selection-search.ts";
|
|
6
6
|
|
|
7
7
|
const icons = { editor: PencilRuler, graph: Network, runs: History } as const;
|
|
@@ -34,7 +34,7 @@ const labels = {
|
|
|
34
34
|
* are — every destination stays one click, and every button keeps one name.
|
|
35
35
|
*/
|
|
36
36
|
export function ViewToggle({ views = ["editor", "graph"] }: { views?: readonly IntelView[] }) {
|
|
37
|
-
const
|
|
37
|
+
const i18n = useI18n();
|
|
38
38
|
const navigate = useNavigate();
|
|
39
39
|
const state = useRouterState({
|
|
40
40
|
select: (current) => ({
|
package/src/archive/archive.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Flow, Node } from "@anchrd/intel-contract";
|
|
2
2
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
3
3
|
import { ArchiveRestore } from "lucide-react";
|
|
4
|
+
import { useI18n } from "@/i18n/i18n-context.tsx";
|
|
4
5
|
import { kindIcons } from "@/kind-icon.ts";
|
|
5
6
|
import { useIntelRouterContext } from "@/router/router-context.ts";
|
|
6
7
|
|
|
@@ -17,7 +18,8 @@ interface ArchivedEntry {
|
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
export function Archive() {
|
|
20
|
-
const { data
|
|
21
|
+
const { data } = useIntelRouterContext();
|
|
22
|
+
const i18n = useI18n();
|
|
21
23
|
const queryClient = useQueryClient();
|
|
22
24
|
|
|
23
25
|
const archived = useQuery({
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Popover as PopoverPrimitive } from "radix-ui";
|
|
4
|
+
import type * as React from "react";
|
|
5
|
+
import { cn } from "@/lib/utils";
|
|
6
|
+
|
|
7
|
+
function Popover({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Root>) {
|
|
8
|
+
return <PopoverPrimitive.Root data-slot="popover" {...props} />;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function PopoverTrigger({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
|
|
12
|
+
return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function PopoverContent({
|
|
16
|
+
className,
|
|
17
|
+
align = "center",
|
|
18
|
+
sideOffset = 4,
|
|
19
|
+
...props
|
|
20
|
+
}: React.ComponentProps<typeof PopoverPrimitive.Content>) {
|
|
21
|
+
return (
|
|
22
|
+
<PopoverPrimitive.Portal>
|
|
23
|
+
<PopoverPrimitive.Content
|
|
24
|
+
data-slot="popover-content"
|
|
25
|
+
align={align}
|
|
26
|
+
sideOffset={sideOffset}
|
|
27
|
+
className={cn(
|
|
28
|
+
"z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-hidden 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-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
|
29
|
+
className,
|
|
30
|
+
)}
|
|
31
|
+
{...props}
|
|
32
|
+
/>
|
|
33
|
+
</PopoverPrimitive.Portal>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function PopoverAnchor({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
|
|
38
|
+
return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export { Popover, PopoverAnchor, PopoverContent, PopoverTrigger };
|
|
@@ -2,6 +2,7 @@ import type { NodeKind } from "@anchrd/intel-contract";
|
|
|
2
2
|
import { useQuery } from "@tanstack/react-query";
|
|
3
3
|
import { ChevronRight, Folder, Search } from "lucide-react";
|
|
4
4
|
import { useMemo, useState } from "react";
|
|
5
|
+
import { useI18n } from "@/i18n/i18n-context.tsx";
|
|
5
6
|
import { useIntelRouterContext } from "@/router/router-context.ts";
|
|
6
7
|
|
|
7
8
|
// What this picker offers, whichever side of the shared tree it came from. Deliberately the four
|
|
@@ -40,7 +41,8 @@ export function EntryPicker({
|
|
|
40
41
|
onSelect(entryId: string, entryKind: PickerKind): void;
|
|
41
42
|
label: string;
|
|
42
43
|
}) {
|
|
43
|
-
const { data
|
|
44
|
+
const { data } = useIntelRouterContext();
|
|
45
|
+
const i18n = useI18n();
|
|
44
46
|
const [query, setQuery] = useState("");
|
|
45
47
|
const [openFolder, setOpenFolder] = useState<string | null>(null);
|
|
46
48
|
const graph = useQuery({
|
|
@@ -3,6 +3,7 @@ import { useInfiniteQuery, useQuery } from "@tanstack/react-query";
|
|
|
3
3
|
import { ChevronDown, ChevronRight, CornerDownRight } from "lucide-react";
|
|
4
4
|
import { useState } from "react";
|
|
5
5
|
import type { I18n } from "@/i18n/i18n.types.ts";
|
|
6
|
+
import { useI18n } from "@/i18n/i18n-context.tsx";
|
|
6
7
|
import { useIntelRouterContext } from "@/router/router-context.ts";
|
|
7
8
|
|
|
8
9
|
// One page is what a person reads before deciding, not what a database can return. The server caps
|
|
@@ -28,7 +29,8 @@ function formatDuration(ms: number, i18n: I18n): string {
|
|
|
28
29
|
* whoever started it — and the person reading this list is not necessarily that person.
|
|
29
30
|
*/
|
|
30
31
|
export function FlowRuns({ flowId }: { flowId: string }) {
|
|
31
|
-
const { data
|
|
32
|
+
const { data } = useIntelRouterContext();
|
|
33
|
+
const i18n = useI18n();
|
|
32
34
|
const [failedOnly, setFailedOnly] = useState(false);
|
|
33
35
|
const [openRunId, setOpenRunId] = useState<string | null>(null);
|
|
34
36
|
// The filter belongs in the key: "only the failed ones" is a different question with a different
|
|
@@ -112,7 +114,7 @@ export function FlowRuns({ flowId }: { flowId: string }) {
|
|
|
112
114
|
}
|
|
113
115
|
|
|
114
116
|
function RunRow({ run, open, toggle }: { run: FlowRunSummary; open: boolean; toggle(): void }) {
|
|
115
|
-
const
|
|
117
|
+
const i18n = useI18n();
|
|
116
118
|
const failed = run.status === "failed";
|
|
117
119
|
const Chevron = open ? ChevronDown : ChevronRight;
|
|
118
120
|
return (
|
|
@@ -160,7 +162,8 @@ function RunRow({ run, open, toggle }: { run: FlowRunSummary; open: boolean; tog
|
|
|
160
162
|
// onto the canvas because a run took an immutable version and the editor shows the current one —
|
|
161
163
|
// marking a node in the wrong graph would point confidently at the wrong step.
|
|
162
164
|
function RunSteps({ runId }: { runId: string }) {
|
|
163
|
-
const { data
|
|
165
|
+
const { data } = useIntelRouterContext();
|
|
166
|
+
const i18n = useI18n();
|
|
164
167
|
const history = useQuery({
|
|
165
168
|
queryKey: ["flow-run-steps", runId],
|
|
166
169
|
queryFn: () => data.getFlowRunSteps(runId),
|
package/src/flows/flows.tsx
CHANGED
|
@@ -32,11 +32,12 @@ import { FlowRuns } from "@/flow-runs/flow-runs.tsx";
|
|
|
32
32
|
import { nodeIcon } from "@/flows/node-icon/node-icon.ts";
|
|
33
33
|
import { NodePalette, usePaletteOpen } from "@/flows/node-palette/node-palette.tsx";
|
|
34
34
|
import { GraphPane } from "@/graph-pane/graph-pane.tsx";
|
|
35
|
+
import { useI18n } from "@/i18n/i18n-context.tsx";
|
|
35
36
|
import { Modal } from "@/modal/modal.tsx";
|
|
36
37
|
import { useIntelRouterContext } from "@/router/router-context.ts";
|
|
37
38
|
import { selectedFrom, viewFrom } from "@/router/selection-search.ts";
|
|
38
39
|
import { SaveButton, UnsavedChangesGuard } from "@/save-button/save-button.tsx";
|
|
39
|
-
import {
|
|
40
|
+
import { useResolvedTheme } from "@/theme/theme-context.tsx";
|
|
40
41
|
import { TitleRow } from "@/title-row/title-row.tsx";
|
|
41
42
|
|
|
42
43
|
type CanvasNode = ReactFlowNode<{ node: FlowNode }, "intel">;
|
|
@@ -89,7 +90,7 @@ const flowKindOfEntry: Record<string, FlowNode["kind"] | undefined> = {
|
|
|
89
90
|
};
|
|
90
91
|
|
|
91
92
|
function FlowCard({ data, selected }: NodeProps<CanvasNode>) {
|
|
92
|
-
const
|
|
93
|
+
const i18n = useI18n();
|
|
93
94
|
const contract = data.node;
|
|
94
95
|
const Icon = nodeIcon[contract.kind];
|
|
95
96
|
const branching = contract.kind === "condition";
|
|
@@ -354,9 +355,10 @@ export function Flows() {
|
|
|
354
355
|
}
|
|
355
356
|
|
|
356
357
|
function FlowsEditor() {
|
|
357
|
-
const { data
|
|
358
|
+
const { data } = useIntelRouterContext();
|
|
359
|
+
const i18n = useI18n();
|
|
358
360
|
const flow = useReactFlow();
|
|
359
|
-
const theme =
|
|
361
|
+
const theme = useResolvedTheme();
|
|
360
362
|
const queryClient = useQueryClient();
|
|
361
363
|
const navigate = useNavigate();
|
|
362
364
|
const tools = useQuery({
|
|
@@ -714,7 +716,7 @@ function FlowTitle({
|
|
|
714
716
|
onSave(): void;
|
|
715
717
|
onPublish(): void;
|
|
716
718
|
}) {
|
|
717
|
-
const
|
|
719
|
+
const i18n = useI18n();
|
|
718
720
|
// A draft that is already the published one freezes nothing, so publishing it would be a no-op
|
|
719
721
|
// with a dialog in front of it.
|
|
720
722
|
const publishable =
|
|
@@ -775,7 +777,8 @@ function PublishPreview({
|
|
|
775
777
|
close(): void;
|
|
776
778
|
onPublished(): Promise<void>;
|
|
777
779
|
}) {
|
|
778
|
-
const { data
|
|
780
|
+
const { data } = useIntelRouterContext();
|
|
781
|
+
const i18n = useI18n();
|
|
779
782
|
const preview = useQuery({
|
|
780
783
|
queryKey: ["flow-publish-preview", flowId, versionId],
|
|
781
784
|
queryFn: () => data.previewFlowPublish({ flowId, versionId }),
|
|
@@ -858,7 +861,7 @@ function NodeInspector({
|
|
|
858
861
|
tools: Array<{ name: string; title: string | null; fingerprint: string }>;
|
|
859
862
|
flows: Flow[];
|
|
860
863
|
}) {
|
|
861
|
-
const
|
|
864
|
+
const i18n = useI18n();
|
|
862
865
|
if (!node) return <p className="p-5 text-sm text-muted-foreground">{i18n.t("flows.inspect")}</p>;
|
|
863
866
|
const contract = node.data.node;
|
|
864
867
|
return (
|
|
@@ -1001,7 +1004,8 @@ function NodeInspector({
|
|
|
1001
1004
|
// them. A standing "this flow has conflicts" badge is not offered on purpose — for tools it could
|
|
1002
1005
|
// never be true, because the catalog is a live query with each user's own portal token (ADR-0003).
|
|
1003
1006
|
function FlowNeeds({ flowId }: { flowId: string }) {
|
|
1004
|
-
const { data
|
|
1007
|
+
const { data } = useIntelRouterContext();
|
|
1008
|
+
const i18n = useI18n();
|
|
1005
1009
|
const needs = useQuery({
|
|
1006
1010
|
queryKey: ["flow-requirements", flowId],
|
|
1007
1011
|
queryFn: () => data.getFlowRequirements(flowId),
|
|
@@ -1082,7 +1086,7 @@ function SubflowVersionField({
|
|
|
1082
1086
|
configuration: Extract<FlowNode, { kind: "subflow" }>["configuration"];
|
|
1083
1087
|
update(fn: (node: FlowNode) => FlowNode): void;
|
|
1084
1088
|
}) {
|
|
1085
|
-
const
|
|
1089
|
+
const i18n = useI18n();
|
|
1086
1090
|
const mode = configuration.version.mode;
|
|
1087
1091
|
return (
|
|
1088
1092
|
<div className="mt-4">
|
|
@@ -2,8 +2,8 @@ import { flowNodeLayer } from "@anchrd/intel-contract";
|
|
|
2
2
|
import { Plus, X } from "lucide-react";
|
|
3
3
|
import { Fragment, useId, useRef, useState } from "react";
|
|
4
4
|
import { type NodeIcon, nodeIcon } from "@/flows/node-icon/node-icon.ts";
|
|
5
|
+
import { useI18n } from "@/i18n/i18n-context.tsx";
|
|
5
6
|
import { cn } from "@/lib/utils.ts";
|
|
6
|
-
import { useIntelRouterContext } from "@/router/router-context.ts";
|
|
7
7
|
import type { NodePaletteProps, PaletteKind } from "./node-palette.types.ts";
|
|
8
8
|
|
|
9
9
|
const storageKey = "intel.flow-palette";
|
|
@@ -48,7 +48,7 @@ export function NodePalette<K extends PaletteKind>({
|
|
|
48
48
|
add,
|
|
49
49
|
disabledReason,
|
|
50
50
|
}: NodePaletteProps<K>) {
|
|
51
|
-
const
|
|
51
|
+
const i18n = useI18n();
|
|
52
52
|
const listId = useId();
|
|
53
53
|
const triggerRef = useRef<HTMLButtonElement>(null);
|
|
54
54
|
const listRef = useRef<HTMLDivElement>(null);
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
TableRow,
|
|
11
11
|
} from "@/components/ui/table.tsx";
|
|
12
12
|
import type { TreeEntry } from "@/data/intel-data-provider/intel-data-provider.types.ts";
|
|
13
|
+
import { useI18n } from "@/i18n/i18n-context.tsx";
|
|
13
14
|
import { kindIcons } from "@/kind-icon.ts";
|
|
14
15
|
import { ResourceMenu, type ResourceTarget } from "@/resource-menu/resource-menu.tsx";
|
|
15
16
|
import { useIntelRouterContext } from "@/router/router-context.ts";
|
|
@@ -38,7 +39,8 @@ function targetOf(entry: TreeEntry): ResourceTarget {
|
|
|
38
39
|
* not read pictures. A column repeating the icon in prose costs the width the title needs.
|
|
39
40
|
*/
|
|
40
41
|
export function FolderContents({ folderId }: { folderId: string }) {
|
|
41
|
-
const { data
|
|
42
|
+
const { data } = useIntelRouterContext();
|
|
43
|
+
const i18n = useI18n();
|
|
42
44
|
const navigate = useNavigate();
|
|
43
45
|
const level = useQuery({
|
|
44
46
|
queryKey: treeLevelKey(folderId),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { RelationGraph } from "@anchrd/intel-contract";
|
|
2
2
|
import type { UseQueryResult } from "@tanstack/react-query";
|
|
3
3
|
import { lazy, Suspense } from "react";
|
|
4
|
-
import {
|
|
4
|
+
import { useI18n } from "@/i18n/i18n-context.tsx";
|
|
5
5
|
|
|
6
6
|
const RelationGraphView = lazy(async () => ({
|
|
7
7
|
default: (await import("@/node-graph/node-graph.tsx")).RelationGraphView,
|
|
@@ -17,7 +17,7 @@ export function GraphPane({
|
|
|
17
17
|
query: UseQueryResult<RelationGraph>;
|
|
18
18
|
select(node: RelationGraph["nodes"][number]): void;
|
|
19
19
|
}) {
|
|
20
|
-
const
|
|
20
|
+
const i18n = useI18n();
|
|
21
21
|
const loading = <p className="p-6 text-sm text-muted-foreground">{i18n.t("common.loading")}</p>;
|
|
22
22
|
if (query.isPending) return loading;
|
|
23
23
|
if (query.isError || !query.data) {
|