Skip to content

Commit 80639ed

Browse files
committed
fix(desktop): resolve PR #2b typecheck errors post cherry-pick
PR #2b 8 cherry-pick 後の typecheck エラーを修正: - TiptapPromptEditor / QuestionInputOverlay モジュールを upstream から補完 (cherry-pick で取りこぼされた新規ファイル群) - settings/project/$projectId/cloud/{page,secrets/page}.tsx の route string を fork の実 route に合わせて修正 - ChatInputFooter 型 props の不整合解消 - V2WorkspacesList の prop 型調整 - ReviewPanel の upstream comment pane 追加 API を fork の 19 procedure と 併存させる形で統合 - packages/local-db: drizzle-kit generate で 0071_v1_migration_state migration SQL と snapshot を生成 (upstream 0041 は fork 最大 idx 70 と 衝突するため、schema.ts の重複 export を削除して drizzle-kit で再生成) - packages/ui/src/components/ai-elements/prompt-input.tsx の型拡張 bun run typecheck: 全 27/27 pass bun run lint: pass fork 固有機能 (19 procedure, TERMINAL_OPTIONS, SUPERSET_WORKSPACE_NAME, dmg.size 4g, 依存) 全健在
1 parent a510c18 commit 80639ed

27 files changed

Lines changed: 4200 additions & 50 deletions

File tree

apps/desktop/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@
134134
"@tiptap/extension-task-list": "^3.17.1",
135135
"@tiptap/extension-text": "^3.17.1",
136136
"@tiptap/extension-underline": "^3.17.1",
137+
"@tiptap/pm": "^3.17.1",
137138
"@tiptap/react": "^3.17.1",
138139
"@tiptap/starter-kit": "^3.17.1",
139140
"@tiptap/suggestion": "^3.17.1",

apps/desktop/src/lib/trpc/routers/external/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import fs from "node:fs";
2-
import nodePath from "node:path";
32
import { access, readFile, writeFile } from "node:fs/promises";
3+
import nodePath from "node:path";
44
import {
55
EXTERNAL_APPS,
66
NON_EDITOR_APPS,
@@ -82,7 +82,7 @@ async function assertPathExists(filePath: string): Promise<void> {
8282
}
8383
}
8484

85-
function normalizeOpenInAppError(error: unknown): never {
85+
function _normalizeOpenInAppError(error: unknown): never {
8686
if (error instanceof TRPCError) {
8787
throw error;
8888
}

apps/desktop/src/lib/trpc/routers/workspaces/procedures/query.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ export const createQueryProcedures = () => {
190190
mainRepoPath: string;
191191
hideImage: boolean;
192192
iconUrl: string | null;
193+
neonProjectId: string | null;
193194
};
194195
workspaces: WorkspaceItem[];
195196
sections: SectionItem[];
@@ -222,6 +223,7 @@ export const createQueryProcedures = () => {
222223
mainRepoPath: project.mainRepoPath,
223224
hideImage: project.hideImage ?? false,
224225
iconUrl: project.iconUrl ?? null,
226+
neonProjectId: project.neonProjectId ?? null,
225227
},
226228
workspaces: [],
227229
sections: projectSections,

apps/desktop/src/renderer/components/Chat/ChatInterface/components/ChatInputFooter/ChatInputFooter.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ interface ChatInputFooterProps {
3939
setPermissionMode: React.Dispatch<React.SetStateAction<PermissionMode>>;
4040
thinkingLevel: ThinkingLevel;
4141
setThinkingLevel: (level: ThinkingLevel) => void;
42+
thinkingDisabledLevels?: Partial<Record<ThinkingLevel, string>>;
43+
thinkingHint?: string;
4244
slashCommands: SlashCommand[];
4345
submitDisabled?: boolean;
4446
renderAttachment?: (file: FileUIPart & { id: string }) => ReactNode;
@@ -54,6 +56,7 @@ interface ChatInputFooterProps {
5456
isQuestionSubmitting?: boolean;
5557
onQuestionRespond?: (questionId: string, answer: string) => Promise<void>;
5658
onQuestionCancel?: () => void;
59+
onSlashCommandSend?: (command: SlashCommand) => void;
5760
}
5861

5962
export function ChatInputFooter({
@@ -71,6 +74,8 @@ export function ChatInputFooter({
7174
setPermissionMode,
7275
thinkingLevel,
7376
setThinkingLevel,
77+
thinkingDisabledLevels,
78+
thinkingHint,
7479
slashCommands,
7580
submitDisabled,
7681
renderAttachment,
@@ -193,6 +198,8 @@ export function ChatInputFooter({
193198
setPermissionMode={setPermissionMode}
194199
thinkingLevel={thinkingLevel}
195200
setThinkingLevel={setThinkingLevel}
201+
thinkingDisabledLevels={thinkingDisabledLevels}
202+
thinkingHint={thinkingHint}
196203
canAbort={canAbort}
197204
submitStatus={submitStatus}
198205
submitDisabled={submitDisabled}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
import { cn } from "@superset/ui/lib/utils";
2+
import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip";
3+
import { ArrowUpIcon, Loader2Icon, PencilIcon, XIcon } from "lucide-react";
4+
import { useEffect, useRef, useState } from "react";
5+
6+
type QuestionOption = { label: string; description?: string };
7+
8+
interface QuestionInputOverlayProps {
9+
question: {
10+
questionId: string;
11+
question: string;
12+
options?: QuestionOption[];
13+
};
14+
isSubmitting: boolean;
15+
onRespond: (questionId: string, answer: string) => Promise<void>;
16+
onCancel: () => void;
17+
}
18+
19+
export function QuestionInputOverlay({
20+
question,
21+
isSubmitting,
22+
onRespond,
23+
onCancel,
24+
}: QuestionInputOverlayProps) {
25+
const [customText, setCustomText] = useState("");
26+
// Tracks which label was submitted: an option label, "__custom__", or "__skip__".
27+
// null = nothing submitted yet.
28+
const [submittedLabel, setSubmittedLabel] = useState<string | null>(null);
29+
const inputRef = useRef<HTMLInputElement>(null);
30+
31+
// biome-ignore lint/correctness/useExhaustiveDependencies: question.questionId is an intentional re-run trigger
32+
useEffect(() => {
33+
setSubmittedLabel(null);
34+
setCustomText("");
35+
}, [question.questionId]);
36+
37+
const options = question.options ?? [];
38+
const submitted = submittedLabel !== null;
39+
const isDisabled = isSubmitting || submitted;
40+
const hasCustomText = customText.trim().length > 0;
41+
// Spinner goes on the pencil icon when the answer came from the text input row.
42+
const isInputRowSubmitted =
43+
submitted && !options.some((o) => o.label === submittedLabel);
44+
45+
const handleSubmitAnswer = (answer: string, label: string) => {
46+
if (isDisabled) return;
47+
setSubmittedLabel(label);
48+
onRespond(question.questionId, answer).catch(() => {
49+
setSubmittedLabel(null);
50+
setCustomText("");
51+
});
52+
};
53+
54+
const handleOption = (label: string) => handleSubmitAnswer(label, label);
55+
const handleCustom = () => {
56+
const trimmed = customText.trim();
57+
if (!trimmed) return;
58+
handleSubmitAnswer(trimmed, "__custom__");
59+
};
60+
const handleSkip = () => handleSubmitAnswer("skip", "__skip__");
61+
62+
return (
63+
<div className="flex max-h-[300px] flex-col overflow-hidden rounded-[13px] border-[0.5px] border-border bg-foreground/[0.02]">
64+
{/* Question — pinned header */}
65+
<div className="flex shrink-0 items-start gap-2 px-3 pt-3 pb-3">
66+
<p className="flex-1 text-sm leading-snug text-foreground">
67+
{question.question}
68+
</p>
69+
<Tooltip>
70+
<TooltipTrigger asChild>
71+
<button
72+
type="button"
73+
className="-mr-0.5 shrink-0 rounded-md p-1 text-muted-foreground/50 transition-colors hover:bg-muted/40 hover:text-muted-foreground"
74+
onClick={onCancel}
75+
aria-label="Cancel"
76+
>
77+
<XIcon className="h-3.5 w-3.5" />
78+
</button>
79+
</TooltipTrigger>
80+
<TooltipContent>Cancel</TooltipContent>
81+
</Tooltip>
82+
</div>
83+
84+
{/* Options — scrollable */}
85+
{options.length > 0 && (
86+
<div
87+
className={cn(
88+
"overflow-y-auto px-2 transition-opacity duration-200",
89+
hasCustomText && !submitted && "opacity-25",
90+
)}
91+
>
92+
{options.map((option, i) => {
93+
const isChosen = submittedLabel === option.label;
94+
return (
95+
<div key={option.label} className="border-t border-border/60">
96+
<button
97+
type="button"
98+
className={cn(
99+
"group flex w-full items-center gap-3 rounded-lg px-2 py-2.5 text-left transition-colors",
100+
isChosen ? "bg-foreground/[0.06]" : "hover:bg-muted/40",
101+
isDisabled && !isChosen && "cursor-not-allowed opacity-40",
102+
)}
103+
disabled={isDisabled}
104+
onClick={() => handleOption(option.label)}
105+
>
106+
<span className="flex size-6 shrink-0 items-center justify-center rounded-[3px] bg-muted/60 font-mono text-xs leading-none text-muted-foreground/70">
107+
{isChosen ? (
108+
<Loader2Icon className="size-3.5 animate-spin" />
109+
) : (
110+
i + 1
111+
)}
112+
</span>
113+
<span
114+
className={cn(
115+
"text-sm transition-colors",
116+
isChosen
117+
? "text-foreground"
118+
: "text-muted-foreground group-hover:text-foreground",
119+
)}
120+
>
121+
{option.label}
122+
</span>
123+
</button>
124+
</div>
125+
);
126+
})}
127+
</div>
128+
)}
129+
130+
{/* Text input / skip — pinned footer */}
131+
{/* biome-ignore lint/a11y/useKeyWithClickEvents: click-to-focus affordance */}
132+
<form
133+
className="mx-2 mb-2 mt-px shrink-0 flex cursor-text items-center gap-3 rounded-lg bg-black/20 px-2.5 py-2 ring-1 ring-inset ring-border/60"
134+
onSubmit={(e) => {
135+
e.preventDefault();
136+
handleCustom();
137+
}}
138+
onClick={() => inputRef.current?.focus()}
139+
>
140+
<span className="flex size-6 shrink-0 items-center justify-center rounded-[3px] bg-muted/60">
141+
{isInputRowSubmitted ? (
142+
<Loader2Icon className="size-3.5 animate-spin text-muted-foreground/70" />
143+
) : (
144+
<PencilIcon className="size-3.5 text-muted-foreground/70" />
145+
)}
146+
</span>
147+
<input
148+
ref={inputRef}
149+
value={customText}
150+
onChange={(e) => setCustomText(e.target.value)}
151+
placeholder={
152+
options.length > 0 ? "Something else" : "Type your answer..."
153+
}
154+
disabled={isDisabled}
155+
className="flex-1 cursor-text bg-transparent py-1 text-sm text-foreground outline-none placeholder:text-muted-foreground/40 disabled:cursor-not-allowed"
156+
/>
157+
{!isDisabled && (
158+
<div className="relative shrink-0">
159+
<button
160+
type="button"
161+
className={cn(
162+
"rounded-sm border border-border px-3 py-1 text-xs font-medium text-muted-foreground transition-all duration-150 hover:border-foreground/30 hover:text-foreground",
163+
hasCustomText ? "pointer-events-none opacity-0" : "opacity-100",
164+
)}
165+
onClick={(e) => {
166+
e.stopPropagation();
167+
handleSkip();
168+
}}
169+
>
170+
Skip
171+
</button>
172+
<button
173+
type="submit"
174+
className={cn(
175+
"absolute right-0 top-1/2 -translate-y-1/2 size-[23px] rounded-full bg-foreground p-[5px] transition-all duration-150 hover:bg-foreground/80",
176+
hasCustomText ? "opacity-100" : "pointer-events-none opacity-0",
177+
)}
178+
aria-label="Submit"
179+
onClick={(e) => e.stopPropagation()}
180+
>
181+
<ArrowUpIcon className="size-3.5 text-background" />
182+
</button>
183+
</div>
184+
)}
185+
</form>
186+
</div>
187+
);
188+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { QuestionInputOverlay } from "./QuestionInputOverlay";
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { cn } from "@superset/ui/utils";
2+
import { mergeAttributes, Node } from "@tiptap/core";
3+
import {
4+
type NodeViewProps,
5+
NodeViewWrapper,
6+
ReactNodeViewRenderer,
7+
} from "@tiptap/react";
8+
9+
function FileMentionChip({ node, selected }: NodeViewProps) {
10+
const path = (node.attrs.path as string | null | undefined) ?? "";
11+
const name = path.split("/").pop() || path || "@";
12+
13+
return (
14+
<NodeViewWrapper as="span" className="inline-block align-middle">
15+
<span
16+
contentEditable={false}
17+
className={cn(
18+
"inline-flex items-center gap-1 rounded-md px-1.5 py-0.5 font-mono text-xs text-foreground/90 select-none cursor-default transition-colors",
19+
selected ? "bg-muted-foreground/15" : "bg-muted-foreground/10",
20+
)}
21+
>
22+
<span className="text-muted-foreground">@</span>
23+
<span>{name}</span>
24+
</span>
25+
</NodeViewWrapper>
26+
);
27+
}
28+
29+
export const FileMentionNode = Node.create({
30+
name: "file-mention",
31+
group: "inline",
32+
inline: true,
33+
atom: true,
34+
selectable: true,
35+
draggable: false,
36+
37+
addAttributes() {
38+
return {
39+
path: {
40+
default: null,
41+
parseHTML: (el) => el.getAttribute("data-path"),
42+
renderHTML: (attrs) => ({ "data-path": attrs.path }),
43+
},
44+
};
45+
},
46+
47+
parseHTML() {
48+
return [{ tag: 'span[data-type="file-mention"]' }];
49+
},
50+
51+
renderHTML({ HTMLAttributes }) {
52+
return [
53+
"span",
54+
mergeAttributes({ "data-type": "file-mention" }, HTMLAttributes),
55+
];
56+
},
57+
58+
addNodeView() {
59+
return ReactNodeViewRenderer(FileMentionChip);
60+
},
61+
});

0 commit comments

Comments
 (0)