@heyramzi/ui 3.5.0 → 3.6.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/COMPONENTS.md +1 -0
- package/dist/ai/chat-window/icons.js +43 -0
- package/dist/ai/chat-window/index.js +5 -0
- package/dist/ai/chat-window/labels.js +16 -0
- package/dist/ai/chat-window/reveal.svelte.js +106 -0
- package/dist/ai/chat-window/types.js +4 -0
- package/package.json +1 -1
- package/src/ai/chat-window/ChatGlyph.svelte +38 -0
- package/src/ai/chat-window/ChatGlyph.svelte.d.ts +9 -0
- package/src/ai/chat-window/ChatGptWindow.svelte +636 -0
- package/src/ai/chat-window/ChatGptWindow.svelte.d.ts +25 -0
- package/src/ai/chat-window/ClaudeWindow.svelte +668 -0
- package/src/ai/chat-window/ClaudeWindow.svelte.d.ts +25 -0
- package/src/ai/chat-window/chat-window.test.ts +80 -0
- package/src/ai/chat-window/icons/LICENSE +24 -0
- package/src/ai/chat-window/icons/chatgpt-chevron.svg +2 -0
- package/src/ai/chat-window/icons/chatgpt-copy.svg +2 -0
- package/src/ai/chat-window/icons/chatgpt-mic.svg +2 -0
- package/src/ai/chat-window/icons/chatgpt-more.svg +2 -0
- package/src/ai/chat-window/icons/chatgpt-plus.svg +2 -0
- package/src/ai/chat-window/icons/chatgpt-regenerate.svg +2 -0
- package/src/ai/chat-window/icons/chatgpt-share.svg +2 -0
- package/src/ai/chat-window/icons/chatgpt-sidebar.svg +1 -0
- package/src/ai/chat-window/icons/chatgpt-thumb.svg +2 -0
- package/src/ai/chat-window/icons/chatgpt-voice.svg +2 -0
- package/src/ai/chat-window/icons/claude-chevron.svg +1 -0
- package/src/ai/chat-window/icons/claude-copy.svg +1 -0
- package/src/ai/chat-window/icons/claude-plus.svg +1 -0
- package/src/ai/chat-window/icons/claude-retry.svg +1 -0
- package/src/ai/chat-window/icons/claude-send.svg +1 -0
- package/src/ai/chat-window/icons/claude-sidebar.svg +1 -0
- package/src/ai/chat-window/icons/claude-sliders.svg +1 -0
- package/src/ai/chat-window/icons/claude-spark.svg +1 -0
- package/src/ai/chat-window/icons/claude-thumb.svg +1 -0
- package/src/ai/chat-window/icons.ts +46 -0
- package/src/ai/chat-window/index.ts +26 -0
- package/src/ai/chat-window/labels.ts +39 -0
- package/src/ai/chat-window/reveal.svelte.ts +106 -0
- package/src/ai/chat-window/types.ts +42 -0
package/COMPONENTS.md
CHANGED
|
@@ -125,6 +125,7 @@ Named exports from `@heyramzi/ui/effects/<name>`, e.g.
|
|
|
125
125
|
Imported from `@heyramzi/ui/ai/<name>`, e.g. `import { AiButton } from "@heyramzi/ui/ai/ai-button"`.
|
|
126
126
|
|
|
127
127
|
- `ai-button` — AiButton
|
|
128
|
+
- `chat-window` — ChatGptWindow, ClaudeWindow: still or playing pictures of a ChatGPT or Claude conversation for a marketing page, with a connector call row. `messages` takes user text, assistant text or a snippet, and tool steps; every printed string is a prop
|
|
128
129
|
- `confirmation` — Confirmation, ConfirmationTitle, ConfirmationState (`when` names the kind it shows), ConfirmationActions, ConfirmationAction, type ConfirmationKind
|
|
129
130
|
- `generation-progress` — GenerationProgress
|
|
130
131
|
- `loading-state` — PixelLoader
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Each glyph is a file a CSS mask paints in the current colour. Sources and licences: icons/LICENSE.
|
|
2
|
+
import gptChevron from "../../../src/ai/chat-window/icons/chatgpt-chevron.svg";
|
|
3
|
+
import gptCopy from "../../../src/ai/chat-window/icons/chatgpt-copy.svg";
|
|
4
|
+
import gptMic from "../../../src/ai/chat-window/icons/chatgpt-mic.svg";
|
|
5
|
+
import gptMore from "../../../src/ai/chat-window/icons/chatgpt-more.svg";
|
|
6
|
+
import gptPlus from "../../../src/ai/chat-window/icons/chatgpt-plus.svg";
|
|
7
|
+
import gptRegenerate from "../../../src/ai/chat-window/icons/chatgpt-regenerate.svg";
|
|
8
|
+
import gptShare from "../../../src/ai/chat-window/icons/chatgpt-share.svg";
|
|
9
|
+
import gptSidebar from "../../../src/ai/chat-window/icons/chatgpt-sidebar.svg";
|
|
10
|
+
import gptThumb from "../../../src/ai/chat-window/icons/chatgpt-thumb.svg";
|
|
11
|
+
import gptVoice from "../../../src/ai/chat-window/icons/chatgpt-voice.svg";
|
|
12
|
+
import claudeChevron from "../../../src/ai/chat-window/icons/claude-chevron.svg";
|
|
13
|
+
import claudeCopy from "../../../src/ai/chat-window/icons/claude-copy.svg";
|
|
14
|
+
import claudePlus from "../../../src/ai/chat-window/icons/claude-plus.svg";
|
|
15
|
+
import claudeRetry from "../../../src/ai/chat-window/icons/claude-retry.svg";
|
|
16
|
+
import claudeSend from "../../../src/ai/chat-window/icons/claude-send.svg";
|
|
17
|
+
import claudeSidebar from "../../../src/ai/chat-window/icons/claude-sidebar.svg";
|
|
18
|
+
import claudeSliders from "../../../src/ai/chat-window/icons/claude-sliders.svg";
|
|
19
|
+
import claudeSpark from "../../../src/ai/chat-window/icons/claude-spark.svg";
|
|
20
|
+
import claudeThumb from "../../../src/ai/chat-window/icons/claude-thumb.svg";
|
|
21
|
+
export const CHATGPT_ICONS = {
|
|
22
|
+
chevron: gptChevron,
|
|
23
|
+
copy: gptCopy,
|
|
24
|
+
mic: gptMic,
|
|
25
|
+
more: gptMore,
|
|
26
|
+
plus: gptPlus,
|
|
27
|
+
regenerate: gptRegenerate,
|
|
28
|
+
share: gptShare,
|
|
29
|
+
sidebar: gptSidebar,
|
|
30
|
+
thumb: gptThumb,
|
|
31
|
+
voice: gptVoice
|
|
32
|
+
};
|
|
33
|
+
export const CLAUDE_ICONS = {
|
|
34
|
+
chevron: claudeChevron,
|
|
35
|
+
copy: claudeCopy,
|
|
36
|
+
plus: claudePlus,
|
|
37
|
+
retry: claudeRetry,
|
|
38
|
+
send: claudeSend,
|
|
39
|
+
sidebar: claudeSidebar,
|
|
40
|
+
sliders: claudeSliders,
|
|
41
|
+
spark: claudeSpark,
|
|
42
|
+
thumb: claudeThumb
|
|
43
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import ChatGptWindow from "../../../src/ai/chat-window/ChatGptWindow.svelte";
|
|
2
|
+
import ClaudeWindow from "../../../src/ai/chat-window/ClaudeWindow.svelte";
|
|
3
|
+
export { CHATGPT_LABELS, CLAUDE_LABELS } from "./labels.js";
|
|
4
|
+
export { REVEAL_BEATS, revealSchedule } from "./reveal.svelte.js";
|
|
5
|
+
export { ChatGptWindow, ClaudeWindow };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export const CHATGPT_LABELS = {
|
|
2
|
+
running: "Calling {name}",
|
|
3
|
+
done: "Used {name}",
|
|
4
|
+
share: "Share",
|
|
5
|
+
disclaimer: "ChatGPT can make mistakes. Check important info.",
|
|
6
|
+
userSaid: "You said:",
|
|
7
|
+
assistantSaid: "ChatGPT said:"
|
|
8
|
+
};
|
|
9
|
+
export const CLAUDE_LABELS = {
|
|
10
|
+
running: "Using {name}",
|
|
11
|
+
done: "Used {name}",
|
|
12
|
+
share: "Share",
|
|
13
|
+
disclaimer: "Claude can make mistakes. Please double-check responses.",
|
|
14
|
+
userSaid: "You said:",
|
|
15
|
+
assistantSaid: "Claude said:"
|
|
16
|
+
};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { untrack } from "svelte";
|
|
2
|
+
/** Milliseconds each beat holds, read as one conversation's pacing, not a transition. */
|
|
3
|
+
export const REVEAL_BEATS = {
|
|
4
|
+
lead: 400,
|
|
5
|
+
user: 900,
|
|
6
|
+
thinking: 900,
|
|
7
|
+
stream: 1500,
|
|
8
|
+
running: 1300,
|
|
9
|
+
after: 350
|
|
10
|
+
};
|
|
11
|
+
/** The timeline for a conversation: when each message enters and settles, and when it ends. */
|
|
12
|
+
export function revealSchedule(roles, beats = REVEAL_BEATS) {
|
|
13
|
+
const out = [];
|
|
14
|
+
let t = beats.lead;
|
|
15
|
+
roles.forEach((role, index) => {
|
|
16
|
+
if (role === "user") {
|
|
17
|
+
out.push({
|
|
18
|
+
at: t,
|
|
19
|
+
index,
|
|
20
|
+
phase: "done"
|
|
21
|
+
});
|
|
22
|
+
t += beats.user;
|
|
23
|
+
} else if (role === "assistant") {
|
|
24
|
+
out.push({
|
|
25
|
+
at: t,
|
|
26
|
+
index,
|
|
27
|
+
phase: "thinking"
|
|
28
|
+
});
|
|
29
|
+
t += beats.thinking;
|
|
30
|
+
out.push({
|
|
31
|
+
at: t,
|
|
32
|
+
index,
|
|
33
|
+
phase: "streaming"
|
|
34
|
+
});
|
|
35
|
+
t += beats.stream;
|
|
36
|
+
out.push({
|
|
37
|
+
at: t,
|
|
38
|
+
index,
|
|
39
|
+
phase: "done"
|
|
40
|
+
});
|
|
41
|
+
t += beats.after;
|
|
42
|
+
} else {
|
|
43
|
+
out.push({
|
|
44
|
+
at: t,
|
|
45
|
+
index,
|
|
46
|
+
phase: "running"
|
|
47
|
+
});
|
|
48
|
+
t += beats.running;
|
|
49
|
+
out.push({
|
|
50
|
+
at: t,
|
|
51
|
+
index,
|
|
52
|
+
phase: "done"
|
|
53
|
+
});
|
|
54
|
+
t += beats.after;
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
return {
|
|
58
|
+
beats: out,
|
|
59
|
+
end: t
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Plays a conversation once the window is on screen and `active`, again each time `active` turns on.
|
|
64
|
+
* Renders finished for SSR, no JavaScript and reduced motion.
|
|
65
|
+
*/
|
|
66
|
+
export function createReveal(options) {
|
|
67
|
+
let phases = $state([]);
|
|
68
|
+
let node = $state(null);
|
|
69
|
+
let seen = $state(false);
|
|
70
|
+
$effect(() => {
|
|
71
|
+
const { enabled } = options();
|
|
72
|
+
if (!node || !enabled || seen) return;
|
|
73
|
+
const io = new IntersectionObserver((entries) => {
|
|
74
|
+
if (entries.some((e) => e.isIntersecting)) {
|
|
75
|
+
seen = true;
|
|
76
|
+
io.disconnect();
|
|
77
|
+
}
|
|
78
|
+
}, { threshold: .35 });
|
|
79
|
+
io.observe(node);
|
|
80
|
+
return () => io.disconnect();
|
|
81
|
+
});
|
|
82
|
+
$effect(() => {
|
|
83
|
+
const { enabled, active } = options();
|
|
84
|
+
if (!enabled || !active || !seen) return;
|
|
85
|
+
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
|
|
86
|
+
const { roles, hold, ondone } = untrack(options);
|
|
87
|
+
const { beats, end } = revealSchedule(roles);
|
|
88
|
+
phases = roles.map(() => "hidden");
|
|
89
|
+
const timers = beats.map((b) => setTimeout(() => phases[b.index] = b.phase, b.at));
|
|
90
|
+
timers.push(setTimeout(() => ondone?.(), end + hold));
|
|
91
|
+
return () => {
|
|
92
|
+
timers.forEach(clearTimeout);
|
|
93
|
+
phases = [];
|
|
94
|
+
};
|
|
95
|
+
});
|
|
96
|
+
return {
|
|
97
|
+
/** A message's phase; `done` when nothing is playing. */
|
|
98
|
+
phase: (index) => phases[index] ?? "done",
|
|
99
|
+
/** Attach to the window's root, so the reveal waits until it scrolls into view. */
|
|
100
|
+
attach: (el) => {
|
|
101
|
+
node = el;
|
|
102
|
+
return () => node = null;
|
|
103
|
+
},
|
|
104
|
+
streamMs: REVEAL_BEATS.stream
|
|
105
|
+
};
|
|
106
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heyramzi/ui",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.6.0",
|
|
4
4
|
"description": "Svelte 5 UI for apps: primitives, effects and animations, plus work (tasks and docs), charts, slides, an editor, onboarding and a canvas as subpaths.",
|
|
5
5
|
"homepage": "https://ui.heyramzi.com",
|
|
6
6
|
"license": "MIT",
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// =========================
|
|
3
|
+
// PROPS
|
|
4
|
+
// =========================
|
|
5
|
+
interface Props {
|
|
6
|
+
/** The SVG file's URL, painted through a mask in the current colour. */
|
|
7
|
+
src: string;
|
|
8
|
+
size?: number;
|
|
9
|
+
class?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let { src, size = 20, class: className = "" }: Props = $props();
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<!-- =========================== -->
|
|
16
|
+
<!-- MARKUP -->
|
|
17
|
+
<!-- =========================== -->
|
|
18
|
+
<span
|
|
19
|
+
class="chat-glyph {className}"
|
|
20
|
+
style:--glyph={`url("${src}")`}
|
|
21
|
+
style:--size="{size}px"
|
|
22
|
+
aria-hidden="true"
|
|
23
|
+
></span>
|
|
24
|
+
|
|
25
|
+
<style>
|
|
26
|
+
@layer theme, base, components, utilities;
|
|
27
|
+
|
|
28
|
+
@layer components {
|
|
29
|
+
.chat-glyph {
|
|
30
|
+
display: block;
|
|
31
|
+
flex: none;
|
|
32
|
+
width: var(--size);
|
|
33
|
+
height: var(--size);
|
|
34
|
+
background: currentColor;
|
|
35
|
+
mask: var(--glyph) center / contain no-repeat;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
</style>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
/** The SVG file's URL, painted through a mask in the current colour. */
|
|
3
|
+
src: string;
|
|
4
|
+
size?: number;
|
|
5
|
+
class?: string;
|
|
6
|
+
}
|
|
7
|
+
declare const $$Component: import("svelte").Component<Props, {}, "">;
|
|
8
|
+
type $$Component = ReturnType<typeof $$Component>;
|
|
9
|
+
export default $$Component;
|