feat: v1 — persistent shell, model gateway, artifact improvements (#1462)

This commit is contained in:
dancer 2026-03-20 09:37:02 +00:00 committed by GitHub
parent 3651670fb9
commit f9652b452a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
161 changed files with 5166 additions and 8009 deletions

View file

@ -0,0 +1,114 @@
import OrderedMap from "orderedmap";
import {
DOMParser,
type MarkSpec,
type Node as ProsemirrorNode,
Schema,
} from "prosemirror-model";
import { schema } from "prosemirror-schema-basic";
import { addListNodes } from "prosemirror-schema-list";
import { EditorState } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
import { useEffect, useRef } from "react";
import { renderToString } from "react-dom/server";
import { MessageResponse } from "@/components/ai-elements/message";
import { DiffType, diffEditor } from "@/lib/editor/diff";
const diffSchema = new Schema({
nodes: addListNodes(schema.spec.nodes, "paragraph block*", "block"),
marks: OrderedMap.from({
...schema.spec.marks.toObject(),
diffMark: {
attrs: { type: { default: "" } },
toDOM(mark) {
let className = "";
switch (mark.attrs.type) {
case DiffType.Inserted:
className =
"bg-emerald-500/15 text-emerald-700 dark:text-emerald-300 rounded-sm px-0.5 -mx-0.5";
break;
case DiffType.Deleted:
className =
"bg-red-500/15 line-through text-red-600 dark:text-red-400 rounded-sm px-0.5 -mx-0.5 opacity-70";
break;
default:
className = "";
}
return ["span", { class: className }, 0];
},
} as MarkSpec,
}),
});
function computeDiff(oldDoc: ProsemirrorNode, newDoc: ProsemirrorNode) {
return diffEditor(diffSchema, oldDoc.toJSON(), newDoc.toJSON());
}
type DiffEditorProps = {
oldContent: string;
newContent: string;
};
export const DiffView = ({ oldContent, newContent }: DiffEditorProps) => {
const editorRef = useRef<HTMLDivElement>(null);
const viewRef = useRef<EditorView | null>(null);
useEffect(() => {
if (editorRef.current && !viewRef.current) {
const parser = DOMParser.fromSchema(diffSchema);
const oldHtmlContent = renderToString(
<MessageResponse>{oldContent}</MessageResponse>
);
const newHtmlContent = renderToString(
<MessageResponse>{newContent}</MessageResponse>
);
const oldContainer = document.createElement("div");
oldContainer.innerHTML = oldHtmlContent;
const newContainer = document.createElement("div");
newContainer.innerHTML = newHtmlContent;
const oldDoc = parser.parse(oldContainer);
const newDoc = parser.parse(newContainer);
const diffedDoc = computeDiff(oldDoc, newDoc);
const state = EditorState.create({
doc: diffedDoc,
plugins: [],
});
viewRef.current = new EditorView(editorRef.current, {
state,
editable: () => false,
});
requestAnimationFrame(() => {
const firstDiff = editorRef.current?.querySelector(
"[class*='bg-emerald'], [class*='bg-red']"
);
if (firstDiff) {
firstDiff.scrollIntoView({ behavior: "smooth", block: "center" });
}
});
}
return () => {
if (viewRef.current) {
viewRef.current.destroy();
viewRef.current = null;
}
};
}, [oldContent, newContent]);
return (
<div
className="diff-editor prose dark:prose-invert prose-neutral relative max-w-none"
ref={editorRef}
/>
);
};