mirror of
https://github.com/different-ai/openwork
synced 2026-04-25 17:15:34 +02:00
feat: align attachment uploads and bump 0.4.0
This commit is contained in:
@@ -3,7 +3,12 @@
|
||||
"mcp": {
|
||||
"chrome-devtools": {
|
||||
"type": "local",
|
||||
"command": ["npx", "-y", "chrome-devtools-mcp@latest"]
|
||||
}
|
||||
"command": [
|
||||
"npx",
|
||||
"-y",
|
||||
"chrome-devtools-mcp@latest"
|
||||
]
|
||||
}
|
||||
},
|
||||
"model": "openai/gpt-5.2-codex"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@different-ai/openwork-ui",
|
||||
"private": true,
|
||||
"version": "0.3.7",
|
||||
"version": "0.4.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -328,15 +328,11 @@ export default function App() {
|
||||
}
|
||||
|
||||
for (const attachment of draft.attachments) {
|
||||
if (attachment.kind === "image") {
|
||||
parts.push({ type: "image", image: attachment.dataUrl, mediaType: attachment.mimeType } as Part);
|
||||
continue;
|
||||
}
|
||||
parts.push({
|
||||
type: "file",
|
||||
data: attachment.dataUrl,
|
||||
url: attachment.dataUrl,
|
||||
filename: attachment.name,
|
||||
mediaType: attachment.mimeType,
|
||||
mime: attachment.mimeType,
|
||||
} as Part);
|
||||
}
|
||||
|
||||
@@ -2665,6 +2661,31 @@ export default function App() {
|
||||
setLanguage: setLocale,
|
||||
});
|
||||
|
||||
const searchWorkspaceFiles = async (query: string) => {
|
||||
const trimmed = query.trim();
|
||||
if (!trimmed) return [];
|
||||
if (isDemoMode()) {
|
||||
const lower = trimmed.toLowerCase();
|
||||
return activeWorkingFiles().filter((file) => file.toLowerCase().includes(lower));
|
||||
}
|
||||
const activeClient = client();
|
||||
if (!activeClient) return [];
|
||||
try {
|
||||
const directory = workspaceProjectDir().trim();
|
||||
const result = unwrap(
|
||||
await activeClient.find.files({
|
||||
query: trimmed,
|
||||
dirs: "true",
|
||||
limit: 50,
|
||||
directory: directory || undefined,
|
||||
}),
|
||||
);
|
||||
return result;
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
const sessionProps = () => ({
|
||||
selectedSessionId: activeSessionId(),
|
||||
setView,
|
||||
@@ -2730,6 +2751,7 @@ export default function App() {
|
||||
openCommandRunModal: openRunModal,
|
||||
commandRegistryItems,
|
||||
registerCommand: commandRegistry.registerCommand,
|
||||
searchFiles: searchWorkspaceFiles,
|
||||
onTryNotionPrompt: () => {
|
||||
setPrompt("setup my crm");
|
||||
setTryNotionPromptVisible(false);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { For, Show, createEffect, createMemo, createSignal, onCleanup } from "solid-js";
|
||||
import type { Agent } from "@opencode-ai/sdk/v2/client";
|
||||
import { ArrowRight, AtSign, File, Paperclip, Terminal, X, Zap } from "lucide-solid";
|
||||
import { ArrowRight, AtSign, ChevronDown, File, Paperclip, X, Zap } from "lucide-solid";
|
||||
|
||||
import type { ComposerAttachment, ComposerDraft, ComposerPart, PromptMode } from "../../types";
|
||||
|
||||
@@ -33,6 +33,15 @@ type ComposerProps = {
|
||||
onInsertCommand: (commandId: string) => void;
|
||||
selectedModelLabel: string;
|
||||
onModelClick: () => void;
|
||||
agentLabel: string;
|
||||
selectedAgent: string | null;
|
||||
agentPickerOpen: boolean;
|
||||
agentPickerBusy: boolean;
|
||||
agentPickerError: string | null;
|
||||
agentOptions: Agent[];
|
||||
onToggleAgentPicker: () => void;
|
||||
onSelectAgent: (agent: string | null) => void;
|
||||
setAgentPickerRef: (el: HTMLDivElement) => void;
|
||||
showNotionBanner: boolean;
|
||||
onNotionBannerClick: () => void;
|
||||
toast: string | null;
|
||||
@@ -319,7 +328,9 @@ export default function Composer(props: ComposerProps) {
|
||||
const syncHeight = () => {
|
||||
if (!editorRef) return;
|
||||
editorRef.style.height = "auto";
|
||||
const nextHeight = Math.min(editorRef.scrollHeight, 160);
|
||||
const baseHeight = 24;
|
||||
const scrollHeight = editorRef.scrollHeight || baseHeight;
|
||||
const nextHeight = Math.min(Math.max(scrollHeight, baseHeight), 160);
|
||||
editorRef.style.height = `${nextHeight}px`;
|
||||
editorRef.style.overflowY = editorRef.scrollHeight > 160 ? "auto" : "hidden";
|
||||
};
|
||||
@@ -697,8 +708,6 @@ export default function Composer(props: ComposerProps) {
|
||||
onCleanup(() => window.removeEventListener("openwork:focusPrompt", handler));
|
||||
});
|
||||
|
||||
const modeLabel = createMemo(() => (mode() === "shell" ? "Shell" : "Prompt"));
|
||||
|
||||
return (
|
||||
<div class="p-4 border-t border-gray-6 bg-gray-1 sticky bottom-0 z-20">
|
||||
<div class="max-w-2xl mx-auto">
|
||||
@@ -880,23 +889,11 @@ export default function Composer(props: ComposerProps) {
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<div class="flex items-end gap-3">
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<div
|
||||
class={`flex items-center gap-1 rounded-full px-2 py-1 text-[10px] font-semibold uppercase tracking-wide border ${
|
||||
mode() === "shell"
|
||||
? "border-amber-7/30 bg-amber-7/10 text-amber-12"
|
||||
: "border-gray-6 bg-gray-1/70 text-gray-9"
|
||||
}`}
|
||||
>
|
||||
<Terminal size={11} />
|
||||
<span>{modeLabel()}</span>
|
||||
</div>
|
||||
<Show when={props.isRemoteWorkspace}>
|
||||
<div class="text-[10px] uppercase tracking-wider text-gray-8">Remote workspace</div>
|
||||
<div class="mb-2 text-[10px] uppercase tracking-wider text-gray-8">Remote workspace</div>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
<div class="relative">
|
||||
<Show when={!props.prompt.trim() && !attachments().length}>
|
||||
@@ -917,12 +914,88 @@ export default function Composer(props: ComposerProps) {
|
||||
onKeyUp={updateMentionQuery}
|
||||
onClick={updateMentionQuery}
|
||||
onPaste={handlePaste}
|
||||
class="bg-transparent border-none p-0 text-gray-12 focus:ring-0 text-[15px] leading-relaxed resize-none min-h-[24px] outline-none relative z-10"
|
||||
class="bg-transparent border-none p-0 pb-12 pr-20 text-gray-12 focus:ring-0 text-[15px] leading-relaxed resize-none min-h-[24px] outline-none relative z-10"
|
||||
/>
|
||||
|
||||
<div class="absolute bottom-0 left-0 z-20" ref={props.setAgentPickerRef}>
|
||||
<button
|
||||
type="button"
|
||||
class="flex items-center gap-2 pl-3 pr-2 py-1.5 bg-gray-1/70 border border-gray-6 rounded-lg hover:border-gray-7 hover:bg-gray-3 transition-all group"
|
||||
onClick={props.onToggleAgentPicker}
|
||||
aria-expanded={props.agentPickerOpen}
|
||||
>
|
||||
<div class="p-1 rounded bg-gray-4 text-gray-10">
|
||||
<AtSign size={14} />
|
||||
</div>
|
||||
<div class="flex flex-col items-start mr-2 min-w-0">
|
||||
<span class="text-xs font-medium text-gray-12 leading-none truncate max-w-[10rem]">
|
||||
{props.agentLabel}
|
||||
</span>
|
||||
<span class="text-[10px] text-gray-10 font-mono leading-none">
|
||||
{props.selectedAgent ? "Agent" : "Default"}
|
||||
</span>
|
||||
</div>
|
||||
<ChevronDown size={14} class="text-gray-10 group-hover:text-gray-11" />
|
||||
</button>
|
||||
|
||||
<Show when={props.agentPickerOpen}>
|
||||
<div class="absolute left-0 bottom-full mb-2 w-72 rounded-2xl border border-gray-6 bg-gray-1/95 shadow-2xl backdrop-blur-md overflow-hidden">
|
||||
<div class="px-4 pt-3 pb-2 text-[10px] font-semibold uppercase tracking-[0.2em] text-gray-8 border-b border-gray-6/30">
|
||||
Session agent
|
||||
</div>
|
||||
<div class="max-h-64 overflow-auto p-2 space-y-1">
|
||||
<button
|
||||
type="button"
|
||||
class={`w-full flex items-center justify-between rounded-xl px-3 py-2 text-left text-xs transition-colors ${
|
||||
props.selectedAgent ? "text-gray-11 hover:bg-gray-12/5" : "bg-gray-12/10 text-gray-12"
|
||||
}`}
|
||||
onClick={() => props.onSelectAgent(null)}
|
||||
>
|
||||
<span>Default agent</span>
|
||||
<Show when={!props.selectedAgent}>
|
||||
<span class="text-[10px] uppercase tracking-wider text-gray-9">Active</span>
|
||||
</Show>
|
||||
</button>
|
||||
<Show
|
||||
when={!props.agentPickerBusy}
|
||||
fallback={<div class="px-3 py-2 text-xs text-gray-9">Loading agents...</div>}
|
||||
>
|
||||
<Show
|
||||
when={props.agentOptions.length}
|
||||
fallback={<div class="px-3 py-2 text-xs text-gray-9">No agents available.</div>}
|
||||
>
|
||||
<For each={props.agentOptions}>
|
||||
{(agent: Agent) => (
|
||||
<button
|
||||
type="button"
|
||||
class={`w-full flex items-center justify-between rounded-xl px-3 py-2 text-left text-xs transition-colors ${
|
||||
props.selectedAgent === agent.name
|
||||
? "bg-gray-12/10 text-gray-12"
|
||||
: "text-gray-11 hover:bg-gray-12/5"
|
||||
}`}
|
||||
onClick={() => props.onSelectAgent(agent.name)}
|
||||
>
|
||||
<span>{agent.name}</span>
|
||||
<Show when={props.selectedAgent === agent.name}>
|
||||
<span class="text-[10px] uppercase tracking-wider text-gray-9">Active</span>
|
||||
</Show>
|
||||
</button>
|
||||
)}
|
||||
</For>
|
||||
</Show>
|
||||
</Show>
|
||||
<Show when={props.agentPickerError}>
|
||||
<div class="px-3 py-2 text-xs text-red-11">{props.agentPickerError}</div>
|
||||
</Show>
|
||||
</div>
|
||||
<div class="border-t border-gray-6/40 px-4 py-2 text-[10px] text-gray-9">
|
||||
Tip: use /agent-next or /agent-prev to cycle.
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="absolute bottom-0 right-0 z-20 flex items-center gap-2">
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
@@ -972,5 +1045,7 @@ export default function Composer(props: ComposerProps) {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -39,6 +39,24 @@ type MessageBlockItem = MessageBlock | StepClusterBlock;
|
||||
export default function MessageList(props: MessageListProps) {
|
||||
const [copyingId, setCopyingId] = createSignal<string | null>(null);
|
||||
let copyTimeout: number | undefined;
|
||||
const isAttachmentPart = (part: Part) => {
|
||||
if (part.type !== "file") return false;
|
||||
const url = (part as { url?: string }).url;
|
||||
return typeof url === "string" && !url.startsWith("file://");
|
||||
};
|
||||
const attachmentsForMessage = (message: MessageWithParts) =>
|
||||
message.parts
|
||||
.filter(isAttachmentPart)
|
||||
.map((part) => {
|
||||
const record = part as { url?: string; filename?: string; mime?: string };
|
||||
return {
|
||||
url: record.url ?? "",
|
||||
filename: record.filename ?? "attachment",
|
||||
mime: record.mime ?? "application/octet-stream",
|
||||
};
|
||||
})
|
||||
.filter((attachment) => !!attachment.url);
|
||||
const isImageAttachment = (mime: string) => mime.startsWith("image/");
|
||||
|
||||
onCleanup(() => {
|
||||
if (copyTimeout !== undefined) {
|
||||
@@ -269,6 +287,32 @@ export default function MessageList(props: MessageListProps) {
|
||||
: "max-w-[68ch] text-[15px] leading-7 text-gray-12 group pl-2"
|
||||
}`}
|
||||
>
|
||||
<Show when={attachmentsForMessage(block.message).length > 0}>
|
||||
<div class={block.isUser ? "mb-3 flex flex-wrap gap-2" : "mb-4 flex flex-wrap gap-2"}>
|
||||
<For each={attachmentsForMessage(block.message)}>
|
||||
{(attachment) => (
|
||||
<div class="flex items-center gap-2 rounded-2xl border border-gray-6 bg-gray-1/70 px-3 py-2 text-xs text-gray-11">
|
||||
<Show
|
||||
when={isImageAttachment(attachment.mime)}
|
||||
fallback={<File size={14} class="text-gray-9" />}
|
||||
>
|
||||
<div class="h-12 w-12 rounded-xl bg-gray-2 overflow-hidden border border-gray-6">
|
||||
<img
|
||||
src={attachment.url}
|
||||
alt={attachment.filename}
|
||||
class="h-full w-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
</Show>
|
||||
<div class="max-w-[180px]">
|
||||
<div class="truncate text-gray-12">{attachment.filename}</div>
|
||||
<div class="text-[10px] text-gray-9">{attachment.mime}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</Show>
|
||||
<For each={block.groups}>
|
||||
{(group, idx) => (
|
||||
<div class={idx() === block.groups.length - 1 ? "" : groupSpacing}>
|
||||
|
||||
@@ -127,8 +127,9 @@ export function createSessionStore(options: {
|
||||
const reloadDetectionSet = new Set<string>();
|
||||
|
||||
const skillPathPattern = /[\\/]\.opencode[\\/](skill|skills)[\\/]/i;
|
||||
const opencodeConfigPattern = /(?:^|[\\/])opencode\.json\b/i;
|
||||
const opencodeConfigPattern = /(?:^|[\\/])opencode\.jsonc?\b/i;
|
||||
const opencodePathPattern = /(?:^|[\\/])\.opencode[\\/]/i;
|
||||
const mutatingTools = new Set(["write", "edit", "apply_patch"]);
|
||||
|
||||
const extractSearchText = (value: unknown) => {
|
||||
if (!value) return "";
|
||||
@@ -146,15 +147,37 @@ export function createSessionStore(options: {
|
||||
return null;
|
||||
};
|
||||
|
||||
const detectReloadReasonDeep = (value: unknown): ReloadReason | null => {
|
||||
if (!value) return null;
|
||||
if (typeof value === "string" || typeof value === "number") {
|
||||
return detectReloadReason(value);
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
for (const entry of value) {
|
||||
const reason = detectReloadReasonDeep(entry);
|
||||
if (reason) return reason;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (typeof value === "object") {
|
||||
for (const entry of Object.values(value as Record<string, unknown>)) {
|
||||
const reason = detectReloadReasonDeep(entry);
|
||||
if (reason) return reason;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const detectReloadFromPart = (part: Part): ReloadReason | null => {
|
||||
if (part.type !== "tool") return null;
|
||||
const record = part as Record<string, unknown>;
|
||||
const toolName = typeof record.tool === "string" ? record.tool : "";
|
||||
if (!mutatingTools.has(toolName)) return null;
|
||||
const state = (record.state ?? {}) as Record<string, unknown>;
|
||||
return (
|
||||
detectReloadReason(record.text) ||
|
||||
detectReloadReason(record.path) ||
|
||||
detectReloadReason(record.title) ||
|
||||
detectReloadReason((record.state as { title?: unknown })?.title) ||
|
||||
detectReloadReason((record.state as { output?: unknown })?.output) ||
|
||||
detectReloadReason((record.state as { input?: unknown })?.input)
|
||||
detectReloadReasonDeep(state.input) ||
|
||||
detectReloadReasonDeep(state.patch) ||
|
||||
detectReloadReasonDeep(state.diff)
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ import type {
|
||||
|
||||
import {
|
||||
AlertTriangle,
|
||||
AtSign,
|
||||
ArrowRight,
|
||||
ChevronDown,
|
||||
HardDrive,
|
||||
@@ -94,6 +93,7 @@ export type SessionViewProps = {
|
||||
providers: Provider[];
|
||||
providerConnectedIds: string[];
|
||||
listAgents: () => Promise<Agent[]>;
|
||||
searchFiles: (query: string) => Promise<string[]>;
|
||||
selectedSessionAgent: string | null;
|
||||
setSessionAgent: (sessionId: string, agent: string | null) => void;
|
||||
saveSession: (sessionId: string) => Promise<string>;
|
||||
@@ -553,7 +553,6 @@ export default function SessionView(props: SessionViewProps) {
|
||||
const sessionId = requireSessionId();
|
||||
if (!sessionId) return;
|
||||
props.setSessionAgent(sessionId, agent);
|
||||
setCommandToast(agent ? `Agent set to ${agent}` : "Agent cleared");
|
||||
};
|
||||
|
||||
const cycleAgent = async (direction: "next" | "prev") => {
|
||||
@@ -582,7 +581,6 @@ export default function SessionView(props: SessionViewProps) {
|
||||
return;
|
||||
}
|
||||
props.setSessionAgent(sessionId, nextAgent);
|
||||
setCommandToast(`Agent set to ${nextAgent}`);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "Agent selection failed";
|
||||
setCommandToast(message);
|
||||
@@ -741,7 +739,6 @@ export default function SessionView(props: SessionViewProps) {
|
||||
}
|
||||
|
||||
props.setSessionAgent(sessionId, match.name);
|
||||
setCommandToast(`Agent set to ${match.name}`);
|
||||
clearPrompt();
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "Agent selection failed";
|
||||
@@ -917,12 +914,6 @@ export default function SessionView(props: SessionViewProps) {
|
||||
props.setPrompt(draft.text);
|
||||
};
|
||||
|
||||
const searchFiles = async (query: string) => {
|
||||
const q = query.trim().toLowerCase();
|
||||
if (!q) return [];
|
||||
return props.workingFiles.filter((file) => file.toLowerCase().includes(q));
|
||||
};
|
||||
|
||||
return (
|
||||
<Show
|
||||
when={props.selectedSessionId}
|
||||
@@ -970,91 +961,6 @@ export default function SessionView(props: SessionViewProps) {
|
||||
</Show>
|
||||
|
||||
</div>
|
||||
<div class="relative flex items-center gap-2" ref={(el) => (agentPickerRef = el)}>
|
||||
<button
|
||||
type="button"
|
||||
class="flex items-center gap-2 pl-3 pr-2 py-1.5 bg-gray-2 border border-gray-6 rounded-lg hover:border-gray-7 hover:bg-gray-4 transition-all group"
|
||||
onClick={openAgentPicker}
|
||||
aria-expanded={agentPickerOpen()}
|
||||
>
|
||||
<div class="p-1 rounded bg-gray-4 text-gray-10">
|
||||
<AtSign size={14} />
|
||||
</div>
|
||||
<div class="flex flex-col items-start mr-2 min-w-0">
|
||||
<span class="text-xs font-medium text-gray-12 leading-none truncate max-w-[10rem]">
|
||||
{agentLabel()}
|
||||
</span>
|
||||
<span class="text-[10px] text-gray-10 font-mono leading-none">
|
||||
{props.selectedSessionAgent ? "Agent" : "Default"}
|
||||
</span>
|
||||
</div>
|
||||
<ChevronDown size={14} class="text-gray-10 group-hover:text-gray-11" />
|
||||
</button>
|
||||
|
||||
<Show when={agentPickerOpen()}>
|
||||
<div class="absolute right-0 top-full mt-2 w-72 rounded-2xl border border-gray-6 bg-gray-1/95 shadow-2xl backdrop-blur-md overflow-hidden">
|
||||
<div class="px-4 pt-3 pb-2 text-[10px] font-semibold uppercase tracking-[0.2em] text-gray-8 border-b border-gray-6/30">
|
||||
Session agent
|
||||
</div>
|
||||
<div class="max-h-64 overflow-auto p-2 space-y-1">
|
||||
<button
|
||||
type="button"
|
||||
class={`w-full flex items-center justify-between rounded-xl px-3 py-2 text-left text-xs transition-colors ${
|
||||
props.selectedSessionAgent
|
||||
? "text-gray-11 hover:bg-gray-12/5"
|
||||
: "bg-gray-12/10 text-gray-12"
|
||||
}`}
|
||||
onClick={() => {
|
||||
applySessionAgent(null);
|
||||
setAgentPickerOpen(false);
|
||||
}}
|
||||
>
|
||||
<span>Default agent</span>
|
||||
<Show when={!props.selectedSessionAgent}>
|
||||
<span class="text-[10px] uppercase tracking-wider text-gray-9">Active</span>
|
||||
</Show>
|
||||
</button>
|
||||
<Show
|
||||
when={!agentPickerBusy()}
|
||||
fallback={<div class="px-3 py-2 text-xs text-gray-9">Loading agents...</div>}
|
||||
>
|
||||
<Show
|
||||
when={agentOptions().length}
|
||||
fallback={<div class="px-3 py-2 text-xs text-gray-9">No agents available.</div>}
|
||||
>
|
||||
<For each={agentOptions()}>
|
||||
{(agent: Agent) => (
|
||||
<button
|
||||
type="button"
|
||||
class={`w-full flex items-center justify-between rounded-xl px-3 py-2 text-left text-xs transition-colors ${
|
||||
props.selectedSessionAgent === agent.name
|
||||
? "bg-gray-12/10 text-gray-12"
|
||||
: "text-gray-11 hover:bg-gray-12/5"
|
||||
}`}
|
||||
onClick={() => {
|
||||
applySessionAgent(agent.name);
|
||||
setAgentPickerOpen(false);
|
||||
}}
|
||||
>
|
||||
<span>{agent.name}</span>
|
||||
<Show when={props.selectedSessionAgent === agent.name}>
|
||||
<span class="text-[10px] uppercase tracking-wider text-gray-9">Active</span>
|
||||
</Show>
|
||||
</button>
|
||||
)}
|
||||
</For>
|
||||
</Show>
|
||||
</Show>
|
||||
<Show when={agentPickerError()}>
|
||||
<div class="px-3 py-2 text-xs text-red-11">{agentPickerError()}</div>
|
||||
</Show>
|
||||
</div>
|
||||
<div class="border-t border-gray-6/40 px-4 py-2 text-[10px] text-gray-9">
|
||||
Tip: use /agent-next or /agent-prev to cycle.
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<Show when={props.error}>
|
||||
@@ -1195,13 +1101,27 @@ export default function SessionView(props: SessionViewProps) {
|
||||
onInsertCommand={handleInsertCommand}
|
||||
selectedModelLabel={props.selectedSessionModelLabel || "Model"}
|
||||
onModelClick={props.openSessionModelPicker}
|
||||
agentLabel={agentLabel()}
|
||||
selectedAgent={props.selectedSessionAgent}
|
||||
agentPickerOpen={agentPickerOpen()}
|
||||
agentPickerBusy={agentPickerBusy()}
|
||||
agentPickerError={agentPickerError()}
|
||||
agentOptions={agentOptions()}
|
||||
onToggleAgentPicker={openAgentPicker}
|
||||
onSelectAgent={(agent) => {
|
||||
applySessionAgent(agent);
|
||||
setAgentPickerOpen(false);
|
||||
}}
|
||||
setAgentPickerRef={(el) => {
|
||||
agentPickerRef = el;
|
||||
}}
|
||||
showNotionBanner={props.showTryNotionPrompt}
|
||||
onNotionBannerClick={props.onTryNotionPrompt}
|
||||
toast={commandToast()}
|
||||
onToast={(message) => setCommandToast(message)}
|
||||
listAgents={props.listAgents}
|
||||
recentFiles={props.workingFiles}
|
||||
searchFiles={searchFiles}
|
||||
searchFiles={props.searchFiles}
|
||||
isRemoteWorkspace={props.activeWorkspaceDisplay.workspaceType === "remote"}
|
||||
/>
|
||||
|
||||
|
||||
@@ -438,7 +438,12 @@ export function groupMessageParts(parts: Part[], messageId: string): MessageGrou
|
||||
}
|
||||
|
||||
if (part.type === "file") {
|
||||
const record = part as { label?: string; path?: string; filename?: string };
|
||||
const record = part as { label?: string; path?: string; filename?: string; url?: string };
|
||||
const url = record.url;
|
||||
if (typeof url === "string" && !url.startsWith("file://")) {
|
||||
flushText();
|
||||
return;
|
||||
}
|
||||
const label = record.label ?? record.path ?? record.filename ?? "";
|
||||
textBuffer += label ? `@${label}` : "@file";
|
||||
return;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@different-ai/openwork",
|
||||
"private": true,
|
||||
"version": "0.3.7",
|
||||
"version": "0.4.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "tauri dev --config \"{\\\"build\\\":{\\\"devUrl\\\":\\\"http://localhost:${PORT:-5173}\\\"}}\"",
|
||||
|
||||
2
packages/desktop/src-tauri/Cargo.lock
generated
2
packages/desktop/src-tauri/Cargo.lock
generated
@@ -2381,7 +2381,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openwork"
|
||||
version = "0.3.7"
|
||||
version = "0.4.0"
|
||||
dependencies = [
|
||||
"json5",
|
||||
"serde",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "openwork"
|
||||
version = "0.3.7"
|
||||
version = "0.4.0"
|
||||
description = "OpenWork"
|
||||
authors = ["Different AI"]
|
||||
edition = "2021"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "OpenWork",
|
||||
"version": "0.3.7",
|
||||
"version": "0.4.0",
|
||||
"identifier": "com.differentai.openwork",
|
||||
"build": {
|
||||
"beforeDevCommand": "pnpm -C ../.. --filter @different-ai/openwork run prepare:sidecar && pnpm -w dev:ui",
|
||||
|
||||
Reference in New Issue
Block a user