hermes-web-ui 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/hermes-web-ui.mjs +114 -19
- package/dist/assets/ChatView-DrzZz5ex.css +1 -0
- package/dist/assets/ChatView-WnNeYrS7.js +38 -0
- package/dist/assets/JobsView-BF9wWdwU.js +831 -0
- package/dist/assets/JobsView-BhwwXuLt.css +1 -0
- package/dist/assets/Modal-CQVLL_Oc.js +276 -0
- package/dist/assets/_plugin-vue_export-helper-D5N3Hfca.js +231 -0
- package/dist/assets/chat-640V6r6N.js +1 -0
- package/dist/assets/client-BxIiwrqC.js +1 -0
- package/dist/assets/index-4iFlrAYJ.js +307 -0
- package/dist/assets/index-CMJKLUKk.css +1 -0
- package/dist/assets/jobs-Cuol6Mqb.js +1 -0
- package/dist/assets/use-message-B8ClBznx.js +117 -0
- package/dist/favicon.ico +0 -0
- package/dist/index.html +21 -0
- package/package.json +3 -11
- package/index.html +0 -13
- package/src/App.vue +0 -54
- package/src/api/chat.ts +0 -87
- package/src/api/client.ts +0 -44
- package/src/api/jobs.ts +0 -100
- package/src/api/system.ts +0 -25
- package/src/assets/hero.png +0 -0
- package/src/assets/vite.svg +0 -1
- package/src/components/chat/ChatInput.vue +0 -123
- package/src/components/chat/ChatPanel.vue +0 -289
- package/src/components/chat/MarkdownRenderer.vue +0 -187
- package/src/components/chat/MessageItem.vue +0 -189
- package/src/components/chat/MessageList.vue +0 -94
- package/src/components/jobs/JobCard.vue +0 -244
- package/src/components/jobs/JobFormModal.vue +0 -188
- package/src/components/jobs/JobsPanel.vue +0 -58
- package/src/components/layout/AppSidebar.vue +0 -169
- package/src/composables/useKeyboard.ts +0 -39
- package/src/env.d.ts +0 -7
- package/src/main.ts +0 -10
- package/src/router/index.ts +0 -24
- package/src/stores/app.ts +0 -66
- package/src/stores/chat.ts +0 -344
- package/src/stores/jobs.ts +0 -72
- package/src/styles/global.scss +0 -60
- package/src/styles/theme.ts +0 -71
- package/src/styles/variables.scss +0 -56
- package/src/views/ChatView.vue +0 -25
- package/src/views/JobsView.vue +0 -93
- package/src/views/SettingsView.vue +0 -257
- package/tsconfig.app.json +0 -17
- package/tsconfig.json +0 -7
- package/tsconfig.node.json +0 -24
- package/vite.config.ts +0 -39
- /package/{assets/logo.png → dist/assets/logo-BAarh-tH.png} +0 -0
- /package/{public → dist}/favicon.svg +0 -0
- /package/{public → dist}/icons.svg +0 -0
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import JobCard from './JobCard.vue'
|
|
3
|
-
import { useJobsStore } from '@/stores/jobs'
|
|
4
|
-
|
|
5
|
-
const emit = defineEmits<{
|
|
6
|
-
edit: [jobId: string]
|
|
7
|
-
}>()
|
|
8
|
-
|
|
9
|
-
const jobsStore = useJobsStore()
|
|
10
|
-
</script>
|
|
11
|
-
|
|
12
|
-
<template>
|
|
13
|
-
<div v-if="jobsStore.jobs.length === 0" class="empty-state">
|
|
14
|
-
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" class="empty-icon">
|
|
15
|
-
<rect x="3" y="4" width="18" height="18" rx="2" ry="2"/>
|
|
16
|
-
<line x1="16" y1="2" x2="16" y2="6"/>
|
|
17
|
-
<line x1="8" y1="2" x2="8" y2="6"/>
|
|
18
|
-
<line x1="3" y1="10" x2="21" y2="10"/>
|
|
19
|
-
</svg>
|
|
20
|
-
<p>No scheduled jobs yet. Create one to get started.</p>
|
|
21
|
-
</div>
|
|
22
|
-
<div v-else class="jobs-grid">
|
|
23
|
-
<JobCard
|
|
24
|
-
v-for="job in jobsStore.jobs"
|
|
25
|
-
:key="job.id"
|
|
26
|
-
:job="job"
|
|
27
|
-
@edit="emit('edit', job.id)"
|
|
28
|
-
/>
|
|
29
|
-
</div>
|
|
30
|
-
</template>
|
|
31
|
-
|
|
32
|
-
<style scoped lang="scss">
|
|
33
|
-
@use '@/styles/variables' as *;
|
|
34
|
-
|
|
35
|
-
.empty-state {
|
|
36
|
-
display: flex;
|
|
37
|
-
flex-direction: column;
|
|
38
|
-
align-items: center;
|
|
39
|
-
justify-content: center;
|
|
40
|
-
height: 100%;
|
|
41
|
-
color: $text-muted;
|
|
42
|
-
gap: 12px;
|
|
43
|
-
|
|
44
|
-
.empty-icon {
|
|
45
|
-
opacity: 0.3;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
p {
|
|
49
|
-
font-size: 14px;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
.jobs-grid {
|
|
54
|
-
display: grid;
|
|
55
|
-
grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
|
|
56
|
-
gap: 14px;
|
|
57
|
-
}
|
|
58
|
-
</style>
|
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import { computed } from 'vue'
|
|
3
|
-
import { useRoute, useRouter } from 'vue-router'
|
|
4
|
-
import { useAppStore } from '@/stores/app'
|
|
5
|
-
|
|
6
|
-
const route = useRoute()
|
|
7
|
-
const router = useRouter()
|
|
8
|
-
const appStore = useAppStore()
|
|
9
|
-
|
|
10
|
-
const selectedKey = computed(() => route.name as string)
|
|
11
|
-
|
|
12
|
-
function handleNav(key: string) {
|
|
13
|
-
router.push({ name: key })
|
|
14
|
-
}
|
|
15
|
-
</script>
|
|
16
|
-
|
|
17
|
-
<template>
|
|
18
|
-
<aside class="sidebar">
|
|
19
|
-
<div class="sidebar-logo" @click="router.push('/')">
|
|
20
|
-
<img src="/assets/logo.png" alt="Hermes" class="logo-img" />
|
|
21
|
-
<span class="logo-text">Hermes</span>
|
|
22
|
-
</div>
|
|
23
|
-
|
|
24
|
-
<nav class="sidebar-nav">
|
|
25
|
-
<button
|
|
26
|
-
class="nav-item"
|
|
27
|
-
:class="{ active: selectedKey === 'chat' }"
|
|
28
|
-
@click="handleNav('chat')"
|
|
29
|
-
>
|
|
30
|
-
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
|
31
|
-
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
|
|
32
|
-
</svg>
|
|
33
|
-
<span>Chat</span>
|
|
34
|
-
</button>
|
|
35
|
-
|
|
36
|
-
<button
|
|
37
|
-
class="nav-item"
|
|
38
|
-
:class="{ active: selectedKey === 'jobs' }"
|
|
39
|
-
@click="handleNav('jobs')"
|
|
40
|
-
>
|
|
41
|
-
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
|
42
|
-
<rect x="3" y="4" width="18" height="18" rx="2" ry="2" />
|
|
43
|
-
<line x1="16" y1="2" x2="16" y2="6" />
|
|
44
|
-
<line x1="8" y1="2" x2="8" y2="6" />
|
|
45
|
-
<line x1="3" y1="10" x2="21" y2="10" />
|
|
46
|
-
</svg>
|
|
47
|
-
<span>Jobs</span>
|
|
48
|
-
</button>
|
|
49
|
-
</nav>
|
|
50
|
-
|
|
51
|
-
<div class="sidebar-footer">
|
|
52
|
-
<div class="status-indicator" :class="{ connected: appStore.connected, disconnected: !appStore.connected }">
|
|
53
|
-
<span class="status-dot"></span>
|
|
54
|
-
<span class="status-text">{{ appStore.connected ? 'Connected' : 'Disconnected' }}</span>
|
|
55
|
-
</div>
|
|
56
|
-
<div class="version-info">Hermes {{ appStore.serverVersion || 'v0.1.0' }}</div>
|
|
57
|
-
</div>
|
|
58
|
-
</aside>
|
|
59
|
-
</template>
|
|
60
|
-
|
|
61
|
-
<style scoped lang="scss">
|
|
62
|
-
@use '@/styles/variables' as *;
|
|
63
|
-
|
|
64
|
-
.sidebar {
|
|
65
|
-
width: $sidebar-width;
|
|
66
|
-
height: 100vh;
|
|
67
|
-
background-color: $bg-sidebar;
|
|
68
|
-
border-right: 1px solid $border-color;
|
|
69
|
-
display: flex;
|
|
70
|
-
flex-direction: column;
|
|
71
|
-
padding: 20px 12px;
|
|
72
|
-
flex-shrink: 0;
|
|
73
|
-
transition: width $transition-normal;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
.logo-img {
|
|
77
|
-
width: 28px;
|
|
78
|
-
height: 28px;
|
|
79
|
-
border-radius: 50%;
|
|
80
|
-
flex-shrink: 0;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
.sidebar-logo {
|
|
84
|
-
display: flex;
|
|
85
|
-
align-items: center;
|
|
86
|
-
gap: 10px;
|
|
87
|
-
padding: 4px 12px 20px;
|
|
88
|
-
color: $text-primary;
|
|
89
|
-
cursor: pointer;
|
|
90
|
-
|
|
91
|
-
.logo-text {
|
|
92
|
-
font-size: 18px;
|
|
93
|
-
font-weight: 600;
|
|
94
|
-
letter-spacing: 0.5px;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
.sidebar-nav {
|
|
99
|
-
flex: 1;
|
|
100
|
-
display: flex;
|
|
101
|
-
flex-direction: column;
|
|
102
|
-
gap: 4px;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
.nav-item {
|
|
106
|
-
display: flex;
|
|
107
|
-
align-items: center;
|
|
108
|
-
gap: 10px;
|
|
109
|
-
padding: 10px 12px;
|
|
110
|
-
border: none;
|
|
111
|
-
background: none;
|
|
112
|
-
color: $text-secondary;
|
|
113
|
-
font-size: 14px;
|
|
114
|
-
border-radius: $radius-sm;
|
|
115
|
-
cursor: pointer;
|
|
116
|
-
transition: all $transition-fast;
|
|
117
|
-
width: 100%;
|
|
118
|
-
text-align: left;
|
|
119
|
-
|
|
120
|
-
&:hover {
|
|
121
|
-
background-color: rgba($accent-primary, 0.06);
|
|
122
|
-
color: $text-primary;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
&.active {
|
|
126
|
-
background-color: rgba($accent-primary, 0.12);
|
|
127
|
-
color: $accent-primary;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
.sidebar-footer {
|
|
132
|
-
padding-top: 16px;
|
|
133
|
-
border-top: 1px solid $border-color;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
.status-indicator {
|
|
137
|
-
display: flex;
|
|
138
|
-
align-items: center;
|
|
139
|
-
gap: 8px;
|
|
140
|
-
padding: 8px 12px;
|
|
141
|
-
font-size: 12px;
|
|
142
|
-
|
|
143
|
-
.status-dot {
|
|
144
|
-
width: 8px;
|
|
145
|
-
height: 8px;
|
|
146
|
-
border-radius: 50%;
|
|
147
|
-
flex-shrink: 0;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
&.connected .status-dot {
|
|
151
|
-
background-color: $success;
|
|
152
|
-
box-shadow: 0 0 6px rgba($success, 0.5);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
&.disconnected .status-dot {
|
|
156
|
-
background-color: $error;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
.status-text {
|
|
160
|
-
color: $text-secondary;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
.version-info {
|
|
165
|
-
padding: 4px 12px;
|
|
166
|
-
font-size: 11px;
|
|
167
|
-
color: $text-muted;
|
|
168
|
-
}
|
|
169
|
-
</style>
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { onMounted, onUnmounted } from 'vue'
|
|
2
|
-
import { useRouter } from 'vue-router'
|
|
3
|
-
import { useChatStore } from '@/stores/chat'
|
|
4
|
-
|
|
5
|
-
export function useKeyboard() {
|
|
6
|
-
const router = useRouter()
|
|
7
|
-
const chatStore = useChatStore()
|
|
8
|
-
|
|
9
|
-
function handleKeydown(e: KeyboardEvent) {
|
|
10
|
-
const mod = e.ctrlKey || e.metaKey
|
|
11
|
-
|
|
12
|
-
if (mod && e.key === 'n') {
|
|
13
|
-
e.preventDefault()
|
|
14
|
-
chatStore.newChat()
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
if (mod && e.key === 'j') {
|
|
18
|
-
e.preventDefault()
|
|
19
|
-
router.push({ name: 'jobs' })
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
if (e.key === 'Escape') {
|
|
23
|
-
// Close any open modals — naive-ui handles this internally
|
|
24
|
-
const modal = document.querySelector('.n-modal-mask')
|
|
25
|
-
if (modal) {
|
|
26
|
-
const closeBtn = modal.querySelector('.n-base-close') as HTMLElement
|
|
27
|
-
closeBtn?.click()
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
onMounted(() => {
|
|
33
|
-
window.addEventListener('keydown', handleKeydown)
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
onUnmounted(() => {
|
|
37
|
-
window.removeEventListener('keydown', handleKeydown)
|
|
38
|
-
})
|
|
39
|
-
}
|
package/src/env.d.ts
DELETED
package/src/main.ts
DELETED
package/src/router/index.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { createRouter, createWebHashHistory } from 'vue-router'
|
|
2
|
-
|
|
3
|
-
const router = createRouter({
|
|
4
|
-
history: createWebHashHistory(),
|
|
5
|
-
routes: [
|
|
6
|
-
{
|
|
7
|
-
path: '/',
|
|
8
|
-
name: 'chat',
|
|
9
|
-
component: () => import('@/views/ChatView.vue'),
|
|
10
|
-
},
|
|
11
|
-
{
|
|
12
|
-
path: '/jobs',
|
|
13
|
-
name: 'jobs',
|
|
14
|
-
component: () => import('@/views/JobsView.vue'),
|
|
15
|
-
},
|
|
16
|
-
{
|
|
17
|
-
path: '/settings',
|
|
18
|
-
name: 'settings',
|
|
19
|
-
redirect: '/',
|
|
20
|
-
},
|
|
21
|
-
],
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
export default router
|
package/src/stores/app.ts
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { defineStore } from 'pinia'
|
|
2
|
-
import { ref } from 'vue'
|
|
3
|
-
import { checkHealth, fetchModels } from '@/api/system'
|
|
4
|
-
import type { Model } from '@/api/system'
|
|
5
|
-
|
|
6
|
-
export const useAppStore = defineStore('app', () => {
|
|
7
|
-
const connected = ref(false)
|
|
8
|
-
const serverVersion = ref('')
|
|
9
|
-
const models = ref<Model[]>([])
|
|
10
|
-
const healthPollTimer = ref<ReturnType<typeof setInterval>>()
|
|
11
|
-
|
|
12
|
-
// Settings
|
|
13
|
-
const streamEnabled = ref(true)
|
|
14
|
-
const sessionPersistence = ref(true)
|
|
15
|
-
const maxTokens = ref(4096)
|
|
16
|
-
const selectedModel = ref('hermes-agent')
|
|
17
|
-
|
|
18
|
-
async function checkConnection() {
|
|
19
|
-
try {
|
|
20
|
-
const res = await checkHealth()
|
|
21
|
-
connected.value = true
|
|
22
|
-
if (res.version) serverVersion.value = res.version
|
|
23
|
-
} catch {
|
|
24
|
-
connected.value = false
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
async function loadModels() {
|
|
29
|
-
try {
|
|
30
|
-
const res = await fetchModels()
|
|
31
|
-
models.value = res.data || []
|
|
32
|
-
if (models.value.length > 0 && !models.value.find(m => m.id === selectedModel.value)) {
|
|
33
|
-
selectedModel.value = models.value[0].id
|
|
34
|
-
}
|
|
35
|
-
} catch {
|
|
36
|
-
// ignore
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function startHealthPolling(interval = 30000) {
|
|
41
|
-
stopHealthPolling()
|
|
42
|
-
checkConnection()
|
|
43
|
-
healthPollTimer.value = setInterval(checkConnection, interval)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function stopHealthPolling() {
|
|
47
|
-
if (healthPollTimer.value) {
|
|
48
|
-
clearInterval(healthPollTimer.value)
|
|
49
|
-
healthPollTimer.value = undefined
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return {
|
|
54
|
-
connected,
|
|
55
|
-
serverVersion,
|
|
56
|
-
models,
|
|
57
|
-
streamEnabled,
|
|
58
|
-
sessionPersistence,
|
|
59
|
-
maxTokens,
|
|
60
|
-
selectedModel,
|
|
61
|
-
checkConnection,
|
|
62
|
-
loadModels,
|
|
63
|
-
startHealthPolling,
|
|
64
|
-
stopHealthPolling,
|
|
65
|
-
}
|
|
66
|
-
})
|