shraga 0.1.147 → 0.1.148
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/dist/client/assets/{index-DzxOqQtK.css → index-BLAQpfJ9.css} +2 -2
- package/dist/client/assets/index-CzkEDvV9.js +2009 -0
- package/dist/client/index.html +2 -2
- package/package.json +2 -5
- package/src/client/components/AssistantMarkdown.tsx +74 -83
- package/src/client/components/ChatView.tsx +2 -2
- package/dist/client/assets/index-C50rEoAa.js +0 -2112
- package/src/client/bare-v3.d.ts +0 -15
- package/src/client/components/AssistantMarkdown.css +0 -18
package/dist/client/index.html
CHANGED
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
14
14
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
15
15
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet" />
|
|
16
|
-
<script type="module" crossorigin src="/assets/index-
|
|
17
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
16
|
+
<script type="module" crossorigin src="/assets/index-CzkEDvV9.js"></script>
|
|
17
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BLAQpfJ9.css">
|
|
18
18
|
</head>
|
|
19
19
|
<body>
|
|
20
20
|
<div id="root"></div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shraga",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.148",
|
|
4
4
|
"description": "The teammate you delegate coding to — a self-hostable, multi-user AI coding agent web UI (Claude Code, with a pluggable engine seam).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -56,7 +56,6 @@
|
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
58
|
"@anthropic-ai/claude-agent-sdk": "^0.3.280",
|
|
59
|
-
"@livx.cc/bare-v3": "0.1.0-alpha.59",
|
|
60
59
|
"@livx.cc/mcp-firebase": "^0.1.14",
|
|
61
60
|
"@radix-ui/react-accordion": "^1.2.2",
|
|
62
61
|
"@radix-ui/react-dialog": "^1.1.4",
|
|
@@ -68,14 +67,12 @@
|
|
|
68
67
|
"clsx": "^2.1.1",
|
|
69
68
|
"cron-parser": "^5.5.0",
|
|
70
69
|
"cronstrue": "^3.14.0",
|
|
71
|
-
"dompurify": "^3.4.16",
|
|
72
70
|
"edge.libx.js": "^0.6.1",
|
|
73
71
|
"express": "^4.21.1",
|
|
74
72
|
"firebase": "^11.0.0",
|
|
75
73
|
"highlight.js": "~11.11.0",
|
|
76
74
|
"lucide-react": "^0.468.0",
|
|
77
|
-
"
|
|
78
|
-
"mcp-slack-use": "github:Livshitz/mcp-slack-use#38ff8b7",
|
|
75
|
+
"mcp-slack-use": "github:Livshitz/mcp-slack-use#ce2bd32",
|
|
79
76
|
"react": "^19.0.0",
|
|
80
77
|
"react-dom": "^19.0.0",
|
|
81
78
|
"react-markdown": "^9.0.0",
|
|
@@ -1,94 +1,85 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
import '
|
|
1
|
+
import { useRef, useState } from 'react';
|
|
2
|
+
import ReactMarkdown from 'react-markdown';
|
|
3
|
+
import remarkGfm from 'remark-gfm';
|
|
4
|
+
import rehypeHighlight from 'rehype-highlight';
|
|
5
|
+
import type { Root, Element } from 'hast';
|
|
6
|
+
import type { Plugin } from 'unified';
|
|
7
|
+
import { Copy, Check } from 'lucide-react';
|
|
7
8
|
import 'highlight.js/styles/github.css';
|
|
8
|
-
import './
|
|
9
|
-
import { NEEDS_AUTH, authedBlobUrl } from './AuthedImage';
|
|
10
|
-
import { useWorkspace } from '@/lib/workspaceContext';
|
|
11
|
-
import { useDarkMode } from '@/hooks/useDarkMode';
|
|
12
|
-
import { logger } from '@/lib/debug';
|
|
9
|
+
import { AuthedImage, NEEDS_AUTH } from './AuthedImage';
|
|
13
10
|
|
|
14
|
-
|
|
11
|
+
// Assistant-reply markdown renderer — a replaceable seam: ChatView imports it as
|
|
12
|
+
// `@/components/AssistantMarkdown`, so a distribution overrides it by shipping a file at that path
|
|
13
|
+
// (the EE build's EE-first `@/` alias). Keep the props contract stable.
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (el.tagName === 'A' && el.hasAttribute('href')) { el.setAttribute('target', '_blank'); el.setAttribute('rel', 'noopener noreferrer'); }
|
|
31
|
-
if (el.tagName === 'IMG') {
|
|
32
|
-
el.setAttribute('class', IMG_CLASS);
|
|
33
|
-
const src = el.getAttribute('src') ?? '';
|
|
34
|
-
// `/uploads/*` 401s without a bearer header — park the src; resolveImages() swaps in a blob URL.
|
|
35
|
-
if (NEEDS_AUTH.test(src)) { el.setAttribute('data-authed-src', src); el.removeAttribute('src'); }
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
const sanitize = (html: string) => purify.sanitize(html);
|
|
39
|
-
|
|
40
|
-
const code = (text: string, lang: string) => {
|
|
41
|
-
const base = codeBlock(text, lang);
|
|
42
|
-
if (!lang || !hljs.getLanguage(lang)) return base;
|
|
43
|
-
const html = hljs.highlight(text, { language: lang, ignoreIllegals: true }).value;
|
|
44
|
-
return base.replace(/<code>[\s\S]*<\/code>/, () => `<code class="hljs language-${lang}">${html}</code>`);
|
|
15
|
+
const RTL_BLOCK_TAGS = new Set(['p', 'li', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'td', 'th']);
|
|
16
|
+
const rehypeBidi: Plugin<[], Root> = () => (tree) => {
|
|
17
|
+
const visit = (node: Root | Element) => {
|
|
18
|
+
for (const child of (node.children ?? [])) {
|
|
19
|
+
if (child.type === 'element') {
|
|
20
|
+
if (RTL_BLOCK_TAGS.has(child.tagName)) {
|
|
21
|
+
child.properties ??= {};
|
|
22
|
+
child.properties.dir = 'auto';
|
|
23
|
+
}
|
|
24
|
+
visit(child);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
visit(tree);
|
|
45
29
|
};
|
|
46
30
|
|
|
47
|
-
export
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const { getToken } = useWorkspace();
|
|
54
|
-
const { dark } = useDarkMode();
|
|
31
|
+
export interface AssistantMarkdownProps {
|
|
32
|
+
text: string;
|
|
33
|
+
/** This block is still streaming (a renderer may use it; this one re-renders whole). */
|
|
34
|
+
streaming?: boolean;
|
|
35
|
+
onImageClick?: (src: string) => void;
|
|
36
|
+
}
|
|
55
37
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
blobs.current.get(src)!.then((url) => { img.src = url; }, (err) => log.error('image load failed:', src, err));
|
|
76
|
-
}
|
|
77
|
-
}, [clean, streaming, view, getToken]);
|
|
38
|
+
export function AssistantMarkdown({ text, onImageClick }: AssistantMarkdownProps) {
|
|
39
|
+
const clean = text.replace(/\[Image #\d+\]\s*/g, '').trim();
|
|
40
|
+
return (
|
|
41
|
+
<ReactMarkdown
|
|
42
|
+
remarkPlugins={[remarkGfm]}
|
|
43
|
+
rehypePlugins={[rehypeHighlight, rehypeBidi]}
|
|
44
|
+
className="prose prose-sm max-w-none dark:prose-invert prose-pre:bg-muted prose-pre:border prose-code:before:content-none prose-code:after:content-none break-words min-w-0"
|
|
45
|
+
components={{
|
|
46
|
+
pre: ({ children, ...props }) => <CodeBlock {...props}>{children}</CodeBlock>,
|
|
47
|
+
a: ({ href, children }) => <a href={href} target="_blank" rel="noopener noreferrer">{children}</a>,
|
|
48
|
+
// Only same-origin /uploads images load; anything else in model output (exfil via a
|
|
49
|
+
// prompt-injected image URL) shows its alt text instead of auto-fetching.
|
|
50
|
+
img: ({ src, alt }) => src && NEEDS_AUTH.test(src)
|
|
51
|
+
? <AuthedImage src={src} alt={alt ?? ''} className="max-h-[80vh] max-w-full rounded-xl border object-contain cursor-pointer hover:opacity-80 transition-opacity" onClick={(s) => onImageClick?.(s)} />
|
|
52
|
+
: <span>{alt}</span>,
|
|
53
|
+
}}
|
|
54
|
+
>{clean}</ReactMarkdown>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
78
57
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
58
|
+
function CodeBlock({ children, ...props }: any) {
|
|
59
|
+
const ref = useRef<HTMLPreElement>(null);
|
|
60
|
+
return (
|
|
61
|
+
<div className="relative group">
|
|
62
|
+
<pre ref={ref} {...props} className="rounded-lg border bg-muted p-4 overflow-x-auto text-xs text-foreground">
|
|
63
|
+
{children}
|
|
64
|
+
</pre>
|
|
65
|
+
<CopyButton getText={() => ref.current?.textContent || ''} />
|
|
66
|
+
</div>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
85
69
|
|
|
70
|
+
function CopyButton({ getText }: { getText: () => string }) {
|
|
71
|
+
const [copied, setCopied] = useState(false);
|
|
86
72
|
return (
|
|
87
|
-
<
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
73
|
+
<button
|
|
74
|
+
onClick={() => {
|
|
75
|
+
navigator.clipboard.writeText(getText());
|
|
76
|
+
setCopied(true);
|
|
77
|
+
setTimeout(() => setCopied(false), 2000);
|
|
78
|
+
}}
|
|
79
|
+
className="absolute top-2 right-2 p-1.5 rounded-md bg-background/80 border opacity-0 group-hover:opacity-100 transition-opacity"
|
|
80
|
+
title="Copy"
|
|
81
|
+
>
|
|
82
|
+
{copied ? <Check className="w-3 h-3" /> : <Copy className="w-3 h-3" />}
|
|
83
|
+
</button>
|
|
93
84
|
);
|
|
94
85
|
}
|
|
@@ -10,12 +10,12 @@ import { AuthedImage, AuthedFileLink } from './AuthedImage';
|
|
|
10
10
|
import { useSlots } from '@/lib/slots';
|
|
11
11
|
import { Dialog, DialogContent } from '@/components/ui/dialog';
|
|
12
12
|
import { ZoomableImage } from './ZoomableImage';
|
|
13
|
-
import { AssistantMarkdown } from '
|
|
13
|
+
import { AssistantMarkdown } from '@/components/AssistantMarkdown';
|
|
14
14
|
|
|
15
15
|
// The appwrap iOS WKWebView wedges its GPU compositor when rendering react-markdown's DOM for a chat with
|
|
16
16
|
// history — JS keeps running but paint + hit-testing freeze for ~1-2min (screen stuck/touch-dead) before
|
|
17
17
|
// WebKit lazily restores. The SAME content in mobile Safari and on desktop is fine, so this is specific to
|
|
18
|
-
// the native WKWebView shell, not the web app.
|
|
18
|
+
// the native WKWebView shell, not the web app. Until the native shell issue is fixed, render assistant
|
|
19
19
|
// messages as PLAIN TEXT inside the native app only; full markdown stays on Safari/desktop/web.
|
|
20
20
|
const IS_APPWRAP_NATIVE = typeof window !== 'undefined' && !!(window as { webkit?: { messageHandlers?: { appwrap?: unknown } } }).webkit?.messageHandlers?.appwrap;
|
|
21
21
|
|