Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
69f221942c | ||
|
|
7749225f71 | ||
|
|
ae322c53cc |
12
package-lock.json
generated
12
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "codenomad-workspace",
|
"name": "codenomad-workspace",
|
||||||
"version": "0.7.0",
|
"version": "0.7.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "codenomad-workspace",
|
"name": "codenomad-workspace",
|
||||||
"version": "0.7.0",
|
"version": "0.7.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"7zip-bin": "^5.2.0",
|
"7zip-bin": "^5.2.0",
|
||||||
"google-auth-library": "^10.5.0"
|
"google-auth-library": "^10.5.0"
|
||||||
@@ -7389,7 +7389,7 @@
|
|||||||
},
|
},
|
||||||
"packages/electron-app": {
|
"packages/electron-app": {
|
||||||
"name": "@neuralnomads/codenomad-electron-app",
|
"name": "@neuralnomads/codenomad-electron-app",
|
||||||
"version": "0.7.0",
|
"version": "0.7.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codenomad/ui": "file:../ui",
|
"@codenomad/ui": "file:../ui",
|
||||||
"@neuralnomads/codenomad": "file:../server"
|
"@neuralnomads/codenomad": "file:../server"
|
||||||
@@ -7423,7 +7423,7 @@
|
|||||||
},
|
},
|
||||||
"packages/server": {
|
"packages/server": {
|
||||||
"name": "@neuralnomads/codenomad",
|
"name": "@neuralnomads/codenomad",
|
||||||
"version": "0.7.0",
|
"version": "0.7.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fastify/cors": "^8.5.0",
|
"@fastify/cors": "^8.5.0",
|
||||||
"@fastify/reply-from": "^9.8.0",
|
"@fastify/reply-from": "^9.8.0",
|
||||||
@@ -7458,14 +7458,14 @@
|
|||||||
},
|
},
|
||||||
"packages/tauri-app": {
|
"packages/tauri-app": {
|
||||||
"name": "@codenomad/tauri-app",
|
"name": "@codenomad/tauri-app",
|
||||||
"version": "0.7.0",
|
"version": "0.7.1",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tauri-apps/cli": "^2.9.4"
|
"@tauri-apps/cli": "^2.9.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/ui": {
|
"packages/ui": {
|
||||||
"name": "@codenomad/ui",
|
"name": "@codenomad/ui",
|
||||||
"version": "0.7.0",
|
"version": "0.7.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@git-diff-view/solid": "^0.0.8",
|
"@git-diff-view/solid": "^0.0.8",
|
||||||
"@kobalte/core": "0.13.11",
|
"@kobalte/core": "0.13.11",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "codenomad-workspace",
|
"name": "codenomad-workspace",
|
||||||
"version": "0.7.0",
|
"version": "0.7.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "CodeNomad monorepo workspace",
|
"description": "CodeNomad monorepo workspace",
|
||||||
"workspaces": {
|
"workspaces": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@neuralnomads/codenomad-electron-app",
|
"name": "@neuralnomads/codenomad-electron-app",
|
||||||
"version": "0.7.0",
|
"version": "0.7.1",
|
||||||
"description": "CodeNomad - AI coding assistant",
|
"description": "CodeNomad - AI coding assistant",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Neural Nomads",
|
"name": "Neural Nomads",
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
"version": "0.5.0",
|
"version": "0.5.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/plugin": "1.1.12"
|
"@opencode-ai/plugin": "1.1.16"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
4
packages/server/package-lock.json
generated
4
packages/server/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@neuralnomads/codenomad",
|
"name": "@neuralnomads/codenomad",
|
||||||
"version": "0.7.0",
|
"version": "0.7.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@neuralnomads/codenomad",
|
"name": "@neuralnomads/codenomad",
|
||||||
"version": "0.7.0",
|
"version": "0.7.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fastify/cors": "^8.5.0",
|
"@fastify/cors": "^8.5.0",
|
||||||
"commander": "^12.1.0",
|
"commander": "^12.1.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@neuralnomads/codenomad",
|
"name": "@neuralnomads/codenomad",
|
||||||
"version": "0.7.0",
|
"version": "0.7.1",
|
||||||
"description": "CodeNomad Server",
|
"description": "CodeNomad Server",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Neural Nomads",
|
"name": "Neural Nomads",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@codenomad/tauri-app",
|
"name": "@codenomad/tauri-app",
|
||||||
"version": "0.7.0",
|
"version": "0.7.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "tauri dev",
|
"dev": "tauri dev",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@codenomad/ui",
|
"name": "@codenomad/ui",
|
||||||
"version": "0.7.0",
|
"version": "0.7.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -82,8 +82,20 @@ interface TaskSessionLocation {
|
|||||||
parentId: string | null
|
parentId: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
function findTaskSessionLocation(sessionId: string): TaskSessionLocation | null {
|
function findTaskSessionLocation(sessionId: string, preferredInstanceId?: string): TaskSessionLocation | null {
|
||||||
if (!sessionId) return null
|
if (!sessionId) return null
|
||||||
|
|
||||||
|
if (preferredInstanceId) {
|
||||||
|
const session = sessions().get(preferredInstanceId)?.get(sessionId)
|
||||||
|
if (session) {
|
||||||
|
return {
|
||||||
|
sessionId: session.id,
|
||||||
|
instanceId: preferredInstanceId,
|
||||||
|
parentId: session.parentId ?? null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const allSessions = sessions()
|
const allSessions = sessions()
|
||||||
for (const [instanceId, sessionMap] of allSessions) {
|
for (const [instanceId, sessionMap] of allSessions) {
|
||||||
const session = sessionMap?.get(sessionId)
|
const session = sessionMap?.get(sessionId)
|
||||||
@@ -440,7 +452,7 @@ export default function MessageBlock(props: MessageBlockProps) {
|
|||||||
const hasToolState =
|
const hasToolState =
|
||||||
Boolean(toolState) && (isToolStateRunning(toolState) || isToolStateCompleted(toolState) || isToolStateError(toolState))
|
Boolean(toolState) && (isToolStateRunning(toolState) || isToolStateCompleted(toolState) || isToolStateError(toolState))
|
||||||
const taskSessionId = hasToolState ? extractTaskSessionId(toolState) : ""
|
const taskSessionId = hasToolState ? extractTaskSessionId(toolState) : ""
|
||||||
const taskLocation = taskSessionId ? findTaskSessionLocation(taskSessionId) : null
|
const taskLocation = taskSessionId ? findTaskSessionLocation(taskSessionId, props.instanceId) : null
|
||||||
const handleGoToTaskSession = (event: MouseEvent) => {
|
const handleGoToTaskSession = (event: MouseEvent) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
setActivePermissionIdForInstance,
|
setActivePermissionIdForInstance,
|
||||||
setActiveQuestionIdForInstance,
|
setActiveQuestionIdForInstance,
|
||||||
} from "../stores/instances"
|
} from "../stores/instances"
|
||||||
import { loadMessages, setActiveSession } from "../stores/sessions"
|
import { ensureSessionParentExpanded, loadMessages, sessions as sessionStateSessions, setActiveSessionFromList } from "../stores/sessions"
|
||||||
import { messageStoreBus } from "../stores/message-v2/bus"
|
import { messageStoreBus } from "../stores/message-v2/bus"
|
||||||
import ToolCall from "./tool-call"
|
import ToolCall from "./tool-call"
|
||||||
|
|
||||||
@@ -201,7 +201,14 @@ const PermissionApprovalModal: Component<PermissionApprovalModalProps> = (props)
|
|||||||
|
|
||||||
function handleGoToSession(sessionId: string) {
|
function handleGoToSession(sessionId: string) {
|
||||||
if (!sessionId) return
|
if (!sessionId) return
|
||||||
setActiveSession(props.instanceId, sessionId)
|
|
||||||
|
const session = sessionStateSessions().get(props.instanceId)?.get(sessionId)
|
||||||
|
const parentId = session?.parentId ?? session?.id
|
||||||
|
if (parentId) {
|
||||||
|
ensureSessionParentExpanded(props.instanceId, parentId)
|
||||||
|
}
|
||||||
|
|
||||||
|
setActiveSessionFromList(props.instanceId, sessionId)
|
||||||
props.onClose()
|
props.onClose()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { Show, For, createMemo, createEffect, type Component } from "solid-js"
|
import { Show, For, createMemo, createEffect, type Component } from "solid-js"
|
||||||
|
import { Expand } from "lucide-solid"
|
||||||
import type { Session } from "../../types/session"
|
import type { Session } from "../../types/session"
|
||||||
import type { Attachment } from "../../types/attachment"
|
import type { Attachment } from "../../types/attachment"
|
||||||
import type { ClientPart } from "../../types/message"
|
import type { ClientPart } from "../../types/message"
|
||||||
import MessageSection from "../message-section"
|
import MessageSection from "../message-section"
|
||||||
import { messageStoreBus } from "../../stores/message-v2/bus"
|
import { messageStoreBus } from "../../stores/message-v2/bus"
|
||||||
import PromptInput from "../prompt-input"
|
import PromptInput from "../prompt-input"
|
||||||
import AttachmentChip from "../attachment-chip"
|
import type { Attachment as PromptAttachment } from "../../types/attachment"
|
||||||
import { getAttachments, removeAttachment } from "../../stores/attachments"
|
import { getAttachments, removeAttachment } from "../../stores/attachments"
|
||||||
import { instances } from "../../stores/instances"
|
import { instances } from "../../stores/instances"
|
||||||
import { loadMessages, sendMessage, forkSession, isSessionMessagesLoading, setActiveParentSession, setActiveSession, runShellCommand, abortSession } from "../../stores/sessions"
|
import { loadMessages, sendMessage, forkSession, isSessionMessagesLoading, setActiveParentSession, setActiveSession, runShellCommand, abortSession } from "../../stores/sessions"
|
||||||
@@ -49,6 +50,54 @@ export const SessionView: Component<SessionViewProps> = (props) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const attachments = createMemo(() => getAttachments(props.instanceId, props.sessionId))
|
const attachments = createMemo(() => getAttachments(props.instanceId, props.sessionId))
|
||||||
|
|
||||||
|
function handleExpandTextAttachment(attachment: PromptAttachment) {
|
||||||
|
if (attachment.source.type !== "text") return
|
||||||
|
|
||||||
|
const textarea = rootRef?.querySelector(".prompt-input") as HTMLTextAreaElement | null
|
||||||
|
const value = attachment.source.value
|
||||||
|
const match = attachment.display.match(/pasted #(\d+)/)
|
||||||
|
const placeholder = match ? `[pasted #${match[1]}]` : null
|
||||||
|
|
||||||
|
const currentText = textarea?.value ?? ""
|
||||||
|
|
||||||
|
let nextText = currentText
|
||||||
|
let selectionTarget: number | null = null
|
||||||
|
|
||||||
|
if (placeholder) {
|
||||||
|
const placeholderIndex = currentText.indexOf(placeholder)
|
||||||
|
if (placeholderIndex !== -1) {
|
||||||
|
nextText =
|
||||||
|
currentText.substring(0, placeholderIndex) +
|
||||||
|
value +
|
||||||
|
currentText.substring(placeholderIndex + placeholder.length)
|
||||||
|
selectionTarget = placeholderIndex + value.length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextText === currentText) {
|
||||||
|
if (textarea) {
|
||||||
|
const start = textarea.selectionStart
|
||||||
|
const end = textarea.selectionEnd
|
||||||
|
nextText = currentText.substring(0, start) + value + currentText.substring(end)
|
||||||
|
selectionTarget = start + value.length
|
||||||
|
} else {
|
||||||
|
nextText = currentText + value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (textarea) {
|
||||||
|
textarea.value = nextText
|
||||||
|
textarea.dispatchEvent(new Event("input", { bubbles: true }))
|
||||||
|
textarea.focus()
|
||||||
|
if (selectionTarget !== null) {
|
||||||
|
textarea.setSelectionRange(selectionTarget, selectionTarget)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
removeAttachment(props.instanceId, props.sessionId, attachment.id)
|
||||||
|
}
|
||||||
|
|
||||||
let scrollToBottomHandle: (() => void) | undefined
|
let scrollToBottomHandle: (() => void) | undefined
|
||||||
let rootRef: HTMLDivElement | undefined
|
let rootRef: HTMLDivElement | undefined
|
||||||
function scheduleScrollToBottom() {
|
function scheduleScrollToBottom() {
|
||||||
@@ -235,14 +284,35 @@ export const SessionView: Component<SessionViewProps> = (props) => {
|
|||||||
|
|
||||||
|
|
||||||
<Show when={attachments().length > 0}>
|
<Show when={attachments().length > 0}>
|
||||||
<div class="flex flex-wrap gap-1.5 border-t px-3 py-2" style="border-color: var(--border-base);">
|
<div class="flex flex-wrap items-center gap-1.5 border-t px-3 py-2" style="border-color: var(--border-base);">
|
||||||
<For each={attachments()}>
|
<For each={attachments()}>
|
||||||
{(attachment) => (
|
{(attachment) => {
|
||||||
<AttachmentChip
|
const isText = attachment.source.type === "text"
|
||||||
attachment={attachment}
|
return (
|
||||||
onRemove={() => removeAttachment(props.instanceId, props.sessionId, attachment.id)}
|
<div class="attachment-chip" title={attachment.source.type === "file" ? attachment.source.path : undefined}>
|
||||||
/>
|
<span class="font-mono">{attachment.display}</span>
|
||||||
)}
|
<Show when={isText}>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="attachment-expand"
|
||||||
|
onClick={() => handleExpandTextAttachment(attachment)}
|
||||||
|
aria-label="Expand pasted text"
|
||||||
|
title="Insert pasted text"
|
||||||
|
>
|
||||||
|
<Expand class="h-3 w-3" aria-hidden="true" />
|
||||||
|
</button>
|
||||||
|
</Show>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="attachment-remove"
|
||||||
|
onClick={() => removeAttachment(props.instanceId, props.sessionId, attachment.id)}
|
||||||
|
aria-label="Remove attachment"
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}}
|
||||||
</For>
|
</For>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|||||||
Reference in New Issue
Block a user