diff --git a/packages/client/src/components/common/RouteLinkItem.vue b/packages/client/src/components/common/RouteLinkItem.vue
new file mode 100644
index 0000000..637027b
--- /dev/null
+++ b/packages/client/src/components/common/RouteLinkItem.vue
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
diff --git a/packages/client/src/components/hermes/chat/ChatPanel.vue b/packages/client/src/components/hermes/chat/ChatPanel.vue
index ef646bb..7414a7e 100644
--- a/packages/client/src/components/hermes/chat/ChatPanel.vue
+++ b/packages/client/src/components/hermes/chat/ChatPanel.vue
@@ -60,6 +60,20 @@ const showSessions = ref(
let mobileQuery: MediaQueryList | null = null;
const isMobile = ref(false);
+function sessionHref(sessionId: string) {
+ const profile = sessionProfile(sessionId);
+ return router.resolve({
+ name: "hermes.session",
+ params: { sessionId },
+ query: profile ? { profile } : undefined,
+ }).href;
+}
+
+function openSessionInNewTab(sessionId: string) {
+ if (typeof window === "undefined") return;
+ window.open(sessionHref(sessionId), "_blank", "noopener,noreferrer");
+}
+
async function handleSessionClick(sessionId: string) {
const session = chatStore.sessions.find((item) => item.id === sessionId);
await router.push({
@@ -442,6 +456,7 @@ const contextMenuOptions = computed(() => {
},
],
})
+ options.push({ label: t("chat.openSessionInNewTab"), key: "open-link" })
options.push({ label: t("chat.copySessionLink"), key: "copy-link" })
options.push({ label: t("chat.copySessionId"), key: "copy-id" })
return options
@@ -478,6 +493,8 @@ async function handleContextMenuSelect(key: string) {
copySessionLink(contextSessionId.value);
} else if (key === "copy-id") {
copySessionId(contextSessionId.value);
+ } else if (key === "open-link") {
+ openSessionInNewTab(contextSessionId.value);
} else if (parseExportKey(key)) {
const { mode, ext } = parseExportKey(key)!;
const loadingMsg = mode === "compressed" ? message.loading(t("chat.exportCompressing"), { duration: 0 }) : null;
@@ -846,6 +863,7 @@ async function handleSessionModelCustomSubmit() {
:selectable="isBatchMode"
:selected="isSessionSelected(s.id)"
:show-profile="true"
+ :to="sessionHref(s.id)"
@select="handleSessionClick(s.id)"
@contextmenu="handleContextMenu($event, s.id)"
@delete="handleDeleteSession(s.id)"
@@ -867,6 +885,7 @@ async function handleSessionModelCustomSubmit() {
:selectable="isBatchMode"
:selected="isSessionSelected(s.id)"
:show-profile="true"
+ :to="sessionHref(s.id)"
@select="handleSessionClick(s.id)"
@contextmenu="handleContextMenu($event, s.id)"
@delete="handleDeleteSession(s.id)"
@@ -1714,6 +1733,7 @@ async function handleSessionModelCustomSubmit() {
border-radius: $radius-sm;
cursor: pointer;
text-align: left;
+ text-decoration: none;
color: $text-secondary;
transition: all $transition-fast;
margin-bottom: 2px;
diff --git a/packages/client/src/components/hermes/chat/SessionListItem.vue b/packages/client/src/components/hermes/chat/SessionListItem.vue
index bf1c0d7..99a7e86 100644
--- a/packages/client/src/components/hermes/chat/SessionListItem.vue
+++ b/packages/client/src/components/hermes/chat/SessionListItem.vue
@@ -17,6 +17,7 @@ const props = withDefaults(defineProps<{
selectable?: boolean
selected?: boolean
showProfile?: boolean
+ to?: string
}>(), {
showProfile: true,
})
@@ -77,11 +78,18 @@ function onTouchMove() {
}
}
-function onClick() {
+function isModifiedNavigation(event?: MouseEvent) {
+ return !!event && (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || event.button !== 0)
+}
+
+function onClick(event?: MouseEvent) {
if (longPressTriggered.value) {
longPressTriggered.value = false
+ event?.preventDefault()
return
}
+ if (isModifiedNavigation(event)) return
+ if (props.to && !props.selectable) event?.preventDefault()
emit('select')
}
@@ -91,10 +99,13 @@ onUnmounted(() => {
-
+