mirror of
https://github.com/agentscope-ai/ReMe.git
synced 2026-09-09 22:31:05 +00:00
* refactor(packaging): reorganize published packages * fix(packaging): install AgentScope extra in wheel smoke * docs: align package guides and documentation site * ci(workflow): add core dependency verification step in Python package build - Add a workflow step to verify released core dependencies by installing the wheel with core extras - Assert the presence of the static index.html file to ensure proper package contents - Create and use a temporary virtual environment for isolation during verification - Keep existing artifacts upload step intact and conditional on inputs.upload_artifacts flag * fix(ci): update package installation dependencies in Windows workflow - Change pip install from editable reme_studio and core to only dev and as extras - Remove installation of reme_studio and core to streamline dependency setup - Ensure Windows CI uses the correct extras for testing environment * fix(tests): add missing commas in toml file reads in package version tests - Added trailing commas in the tomllib.loads calls for auto-fin and daily_paper configs - Ensured consistent syntax to prevent potential tuple misinterpretation - Improved readability and correctness of the test setup code * fix(packaging): protect qwenpaw releases and test Studio health
257 lines
7.8 KiB
TypeScript
257 lines
7.8 KiB
TypeScript
"use client";
|
|
|
|
import { create } from "zustand";
|
|
import { createJSONStorage, persist } from "zustand/middleware";
|
|
import {
|
|
applyStreamChunk,
|
|
finishChatMessage,
|
|
toggleChatBlock,
|
|
} from "./chat-stream";
|
|
import type {
|
|
ChatMessage,
|
|
MemoryGraphRoot,
|
|
StreamChunk,
|
|
WorkspaceTab,
|
|
} from "./types";
|
|
import {
|
|
prepareWorkspaceSnapshot,
|
|
type PersistedWorkspaceState,
|
|
} from "./workspace-persistence";
|
|
import { markMarkdownContentSaved } from "./markdown-save";
|
|
import { unsavedTabsClosedBy } from "./tab-close";
|
|
|
|
interface WorkspaceState {
|
|
tabs: WorkspaceTab[];
|
|
activeTabId?: string;
|
|
openMarkdown: (path: string) => string;
|
|
openAgent: () => string;
|
|
openGraph: (root: MemoryGraphRoot) => string;
|
|
closeTab: (id: string, discardUnsaved?: boolean) => void;
|
|
closeOtherTabs: (id: string, discardUnsaved?: boolean) => void;
|
|
setActiveTab: (id: string) => void;
|
|
hydrateMarkdown: (id: string, content: string, mtime?: string) => void;
|
|
failMarkdown: (id: string, error: string) => void;
|
|
updateMarkdown: (id: string, content: string) => void;
|
|
markSaved: (id: string, content: string, mtime?: string) => void;
|
|
addChatTurn: (
|
|
tabId: string,
|
|
user: ChatMessage,
|
|
assistant: ChatMessage,
|
|
) => void;
|
|
applyChatChunk: (
|
|
tabId: string,
|
|
messageId: string,
|
|
chunk: StreamChunk,
|
|
) => void;
|
|
toggleChatBlock: (
|
|
tabId: string,
|
|
messageId: string,
|
|
blockId: string,
|
|
expanded: boolean,
|
|
) => void;
|
|
finishChat: (tabId: string, messageId: string, error?: string) => void;
|
|
}
|
|
|
|
const fileTitle = (path: string) => path.split("/").pop() || path;
|
|
// Preserve the original key so existing tabs, chats, and unsaved drafts survive the Studio rename.
|
|
const WORKSPACE_STORAGE_KEY = "reme-workspace";
|
|
|
|
export const useWorkspaceStore = create<WorkspaceState>()(
|
|
persist(
|
|
(set, get) => ({
|
|
tabs: [],
|
|
activeTabId: undefined,
|
|
openMarkdown: (path) => {
|
|
const existing = get().tabs.find(
|
|
(tab) => tab.type === "markdown" && tab.path === path,
|
|
);
|
|
if (existing) {
|
|
set({ activeTabId: existing.id });
|
|
return existing.id;
|
|
}
|
|
const id = `file:${path}`;
|
|
set((state) => ({
|
|
tabs: [
|
|
...state.tabs,
|
|
{
|
|
id,
|
|
type: "markdown",
|
|
title: fileTitle(path),
|
|
path,
|
|
content: "",
|
|
savedContent: "",
|
|
loading: true,
|
|
},
|
|
],
|
|
activeTabId: id,
|
|
}));
|
|
return id;
|
|
},
|
|
openAgent: () => {
|
|
const id = `agent:${crypto.randomUUID()}`;
|
|
set((state) => ({
|
|
tabs: [...state.tabs, { id, type: "agent", title: "", messages: [] }],
|
|
activeTabId: id,
|
|
}));
|
|
return id;
|
|
},
|
|
openGraph: (root) => {
|
|
const id = `graph:${root}`;
|
|
const existing = get().tabs.find(
|
|
(tab) => tab.id === id && tab.type === "graph",
|
|
);
|
|
if (existing) {
|
|
set({ activeTabId: id });
|
|
return id;
|
|
}
|
|
set((state) => ({
|
|
tabs: [
|
|
...state.tabs,
|
|
{ id, type: "graph", title: `${root} graph`, root },
|
|
],
|
|
activeTabId: id,
|
|
}));
|
|
return id;
|
|
},
|
|
closeTab: (id, discardUnsaved = false) =>
|
|
set((state) => {
|
|
if (
|
|
!discardUnsaved &&
|
|
unsavedTabsClosedBy(state.tabs, id, false).length
|
|
)
|
|
return state;
|
|
const index = state.tabs.findIndex((tab) => tab.id === id);
|
|
const tabs = state.tabs.filter((tab) => tab.id !== id);
|
|
const activeTabId =
|
|
state.activeTabId === id
|
|
? tabs[Math.max(0, index - 1)]?.id
|
|
: state.activeTabId;
|
|
return { tabs, activeTabId };
|
|
}),
|
|
closeOtherTabs: (id, discardUnsaved = false) =>
|
|
set((state) => {
|
|
if (
|
|
!discardUnsaved &&
|
|
unsavedTabsClosedBy(state.tabs, id, true).length
|
|
)
|
|
return state;
|
|
const tab = state.tabs.find((item) => item.id === id);
|
|
return tab ? { tabs: [tab], activeTabId: id } : state;
|
|
}),
|
|
setActiveTab: (activeTabId) => set({ activeTabId }),
|
|
hydrateMarkdown: (id, content, mtime) =>
|
|
set((state) => ({
|
|
tabs: state.tabs.map((tab) =>
|
|
tab.id === id && tab.type === "markdown"
|
|
? {
|
|
...tab,
|
|
content,
|
|
savedContent: content,
|
|
mtime,
|
|
loading: false,
|
|
error: undefined,
|
|
}
|
|
: tab,
|
|
),
|
|
})),
|
|
failMarkdown: (id, error) =>
|
|
set((state) => ({
|
|
tabs: state.tabs.map((tab) =>
|
|
tab.id === id && tab.type === "markdown"
|
|
? { ...tab, loading: false, error }
|
|
: tab,
|
|
),
|
|
})),
|
|
updateMarkdown: (id, content) =>
|
|
set((state) => ({
|
|
tabs: state.tabs.map((tab) =>
|
|
tab.id === id && tab.type === "markdown"
|
|
? { ...tab, content }
|
|
: tab,
|
|
),
|
|
})),
|
|
markSaved: (id, content, mtime) =>
|
|
set((state) => ({
|
|
tabs: state.tabs.map((tab) =>
|
|
tab.id === id && tab.type === "markdown"
|
|
? markMarkdownContentSaved(tab, content, mtime)
|
|
: tab,
|
|
),
|
|
})),
|
|
addChatTurn: (tabId, user, assistant) =>
|
|
set((state) => ({
|
|
tabs: state.tabs.map((tab) =>
|
|
tab.id === tabId && tab.type === "agent"
|
|
? {
|
|
...tab,
|
|
title: tab.messages.length
|
|
? tab.title
|
|
: user.content.slice(0, 18),
|
|
messages: [...tab.messages, user, assistant],
|
|
streaming: true,
|
|
}
|
|
: tab,
|
|
),
|
|
})),
|
|
applyChatChunk: (tabId, messageId, chunk) =>
|
|
set((state) => ({
|
|
tabs: state.tabs.map((tab) => {
|
|
if (tab.id !== tabId || tab.type !== "agent") return tab;
|
|
const messages = tab.messages.map((message) =>
|
|
message.id === messageId
|
|
? applyStreamChunk(message, chunk)
|
|
: message,
|
|
);
|
|
return {
|
|
...tab,
|
|
sessionId: chunk.session_id || tab.sessionId,
|
|
messages,
|
|
};
|
|
}),
|
|
})),
|
|
toggleChatBlock: (tabId, messageId, blockId, expanded) =>
|
|
set((state) => ({
|
|
tabs: state.tabs.map((tab) =>
|
|
tab.id === tabId && tab.type === "agent"
|
|
? {
|
|
...tab,
|
|
messages: tab.messages.map((message) =>
|
|
message.id === messageId
|
|
? toggleChatBlock(message, blockId, expanded)
|
|
: message,
|
|
),
|
|
}
|
|
: tab,
|
|
),
|
|
})),
|
|
finishChat: (tabId, messageId, error) =>
|
|
set((state) => ({
|
|
tabs: state.tabs.map((tab) =>
|
|
tab.id === tabId && tab.type === "agent"
|
|
? {
|
|
...tab,
|
|
streaming: false,
|
|
messages: tab.messages.map((message) =>
|
|
message.id === messageId
|
|
? finishChatMessage(message, error)
|
|
: message,
|
|
),
|
|
}
|
|
: tab,
|
|
),
|
|
})),
|
|
}),
|
|
{
|
|
name: WORKSPACE_STORAGE_KEY,
|
|
version: 1,
|
|
storage: createJSONStorage(() => localStorage),
|
|
skipHydration: true,
|
|
partialize: (state): PersistedWorkspaceState =>
|
|
prepareWorkspaceSnapshot(state.tabs, state.activeTabId),
|
|
merge: (persisted, current) => ({
|
|
...current,
|
|
...(persisted as PersistedWorkspaceState),
|
|
}),
|
|
},
|
|
),
|
|
);
|