@codori/client 0.0.3 → 0.0.4
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/app/components/ChatWorkspace.vue +30 -3
- package/app/composables/useProjects.ts +42 -0
- package/app/layouts/default.vue +56 -5
- package/app/pages/index.vue +0 -1
- package/package.json +1 -1
- package/server/api/codori/service/update.get.ts +7 -0
- package/server/api/codori/service/update.post.ts +9 -0
- package/shared/codex-chat.ts +47 -4
- package/shared/codori.ts +12 -0
- package/app/components/TunnelNotice.vue +0 -27
|
@@ -16,10 +16,13 @@ import { useProjects } from '../composables/useProjects'
|
|
|
16
16
|
import { useRpc } from '../composables/useRpc'
|
|
17
17
|
import { useChatSubmitGuard } from '../composables/useChatSubmitGuard'
|
|
18
18
|
import {
|
|
19
|
+
hideThinkingPlaceholder,
|
|
19
20
|
ITEM_PART,
|
|
20
21
|
eventToMessage,
|
|
21
22
|
isSubagentActiveStatus,
|
|
22
23
|
itemToMessages,
|
|
24
|
+
replaceStreamingMessage,
|
|
25
|
+
showThinkingPlaceholder,
|
|
23
26
|
threadToMessages,
|
|
24
27
|
upsertStreamingMessage,
|
|
25
28
|
type ChatMessage,
|
|
@@ -505,6 +508,20 @@ const removeOptimisticMessage = (messageId: string) => {
|
|
|
505
508
|
optimisticAttachmentSnapshots.delete(messageId)
|
|
506
509
|
}
|
|
507
510
|
|
|
511
|
+
const clearThinkingPlaceholder = () => {
|
|
512
|
+
messages.value = hideThinkingPlaceholder(messages.value)
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
const ensureThinkingPlaceholder = () => {
|
|
516
|
+
messages.value = showThinkingPlaceholder(messages.value)
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
const clearThinkingPlaceholderForVisibleItem = (item: CodexThreadItem) => {
|
|
520
|
+
if (item.type !== 'userMessage') {
|
|
521
|
+
clearThinkingPlaceholder()
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
508
525
|
const restoreDraftIfPristine = (text: string, submittedAttachments: DraftAttachment[]) => {
|
|
509
526
|
if (!input.value.trim()) {
|
|
510
527
|
input.value = text
|
|
@@ -1231,7 +1248,7 @@ const applySubagentNotification = (threadId: string, notification: CodexRpcNotif
|
|
|
1231
1248
|
}
|
|
1232
1249
|
for (const nextMessage of itemToMessages(params.item)) {
|
|
1233
1250
|
updateSubagentPanelMessages(threadId, (panelMessages) =>
|
|
1234
|
-
|
|
1251
|
+
replaceStreamingMessage(panelMessages, {
|
|
1235
1252
|
...nextMessage,
|
|
1236
1253
|
pending: false
|
|
1237
1254
|
})
|
|
@@ -1433,6 +1450,7 @@ const applyNotification = (notification: CodexRpcNotification) => {
|
|
|
1433
1450
|
status.value = 'streaming'
|
|
1434
1451
|
return
|
|
1435
1452
|
}
|
|
1453
|
+
clearThinkingPlaceholderForVisibleItem(params.item)
|
|
1436
1454
|
seedStreamingMessage(params.item)
|
|
1437
1455
|
status.value = 'streaming'
|
|
1438
1456
|
return
|
|
@@ -1442,6 +1460,7 @@ const applyNotification = (notification: CodexRpcNotification) => {
|
|
|
1442
1460
|
if (params.item.type === 'collabAgentToolCall') {
|
|
1443
1461
|
applySubagentActivityItem(params.item)
|
|
1444
1462
|
}
|
|
1463
|
+
clearThinkingPlaceholderForVisibleItem(params.item)
|
|
1445
1464
|
for (const nextMessage of itemToMessages(params.item)) {
|
|
1446
1465
|
const confirmedMessage = {
|
|
1447
1466
|
...nextMessage,
|
|
@@ -1452,12 +1471,13 @@ const applyNotification = (notification: CodexRpcNotification) => {
|
|
|
1452
1471
|
continue
|
|
1453
1472
|
}
|
|
1454
1473
|
|
|
1455
|
-
messages.value =
|
|
1474
|
+
messages.value = replaceStreamingMessage(messages.value, confirmedMessage)
|
|
1456
1475
|
}
|
|
1457
1476
|
return
|
|
1458
1477
|
}
|
|
1459
1478
|
case 'item/agentMessage/delta': {
|
|
1460
1479
|
const params = notification.params as { itemId: string, delta: string }
|
|
1480
|
+
clearThinkingPlaceholder()
|
|
1461
1481
|
appendTextPartDelta(params.itemId, params.delta, {
|
|
1462
1482
|
id: params.itemId,
|
|
1463
1483
|
role: 'assistant',
|
|
@@ -1473,6 +1493,7 @@ const applyNotification = (notification: CodexRpcNotification) => {
|
|
|
1473
1493
|
}
|
|
1474
1494
|
case 'item/plan/delta': {
|
|
1475
1495
|
const params = notification.params as { itemId: string, delta: string }
|
|
1496
|
+
clearThinkingPlaceholder()
|
|
1476
1497
|
appendTextPartDelta(params.itemId, params.delta, {
|
|
1477
1498
|
id: params.itemId,
|
|
1478
1499
|
role: 'assistant',
|
|
@@ -1489,6 +1510,7 @@ const applyNotification = (notification: CodexRpcNotification) => {
|
|
|
1489
1510
|
case 'item/reasoning/textDelta':
|
|
1490
1511
|
case 'item/reasoning/summaryTextDelta': {
|
|
1491
1512
|
const params = notification.params as { itemId: string, delta: string }
|
|
1513
|
+
clearThinkingPlaceholder()
|
|
1492
1514
|
updateMessage(params.itemId, {
|
|
1493
1515
|
id: params.itemId,
|
|
1494
1516
|
role: 'assistant',
|
|
@@ -1596,6 +1618,7 @@ const applyNotification = (notification: CodexRpcNotification) => {
|
|
|
1596
1618
|
case 'error': {
|
|
1597
1619
|
const params = notification.params as { error?: { message?: string } }
|
|
1598
1620
|
const messageText = params.error?.message ?? 'The stream failed.'
|
|
1621
|
+
clearThinkingPlaceholder()
|
|
1599
1622
|
pushEventMessage('stream.error', messageText)
|
|
1600
1623
|
clearPendingOptimisticMessages(liveStream, { discardSnapshots: true })
|
|
1601
1624
|
clearLiveStream(new Error(messageText))
|
|
@@ -1606,6 +1629,7 @@ const applyNotification = (notification: CodexRpcNotification) => {
|
|
|
1606
1629
|
case 'turn/failed': {
|
|
1607
1630
|
const params = notification.params as { error?: { message?: string } }
|
|
1608
1631
|
const messageText = params.error?.message ?? 'The turn failed.'
|
|
1632
|
+
clearThinkingPlaceholder()
|
|
1609
1633
|
pushEventMessage('turn.failed', messageText)
|
|
1610
1634
|
clearPendingOptimisticMessages(liveStream, { discardSnapshots: true })
|
|
1611
1635
|
clearLiveStream(new Error(messageText))
|
|
@@ -1616,6 +1640,7 @@ const applyNotification = (notification: CodexRpcNotification) => {
|
|
|
1616
1640
|
case 'stream/error': {
|
|
1617
1641
|
const params = notification.params as { message?: string }
|
|
1618
1642
|
const messageText = params.message ?? 'The stream failed.'
|
|
1643
|
+
clearThinkingPlaceholder()
|
|
1619
1644
|
pushEventMessage('stream.error', messageText)
|
|
1620
1645
|
clearPendingOptimisticMessages(liveStream, { discardSnapshots: true })
|
|
1621
1646
|
clearLiveStream(new Error(messageText))
|
|
@@ -1624,6 +1649,7 @@ const applyNotification = (notification: CodexRpcNotification) => {
|
|
|
1624
1649
|
return
|
|
1625
1650
|
}
|
|
1626
1651
|
case 'turn/completed': {
|
|
1652
|
+
clearThinkingPlaceholder()
|
|
1627
1653
|
clearPendingOptimisticMessages(liveStream, { discardSnapshots: true })
|
|
1628
1654
|
error.value = null
|
|
1629
1655
|
status.value = 'ready'
|
|
@@ -1664,6 +1690,7 @@ const sendMessage = async () => {
|
|
|
1664
1690
|
const optimisticMessageId = optimisticMessage.id
|
|
1665
1691
|
rememberOptimisticAttachments(optimisticMessageId, submittedAttachments)
|
|
1666
1692
|
messages.value = [...messages.value, optimisticMessage]
|
|
1693
|
+
ensureThinkingPlaceholder()
|
|
1667
1694
|
let startedLiveStream: LiveStream | null = null
|
|
1668
1695
|
|
|
1669
1696
|
try {
|
|
@@ -1711,6 +1738,7 @@ const sendMessage = async () => {
|
|
|
1711
1738
|
} catch (caughtError) {
|
|
1712
1739
|
const messageText = caughtError instanceof Error ? caughtError.message : String(caughtError)
|
|
1713
1740
|
|
|
1741
|
+
clearThinkingPlaceholder()
|
|
1714
1742
|
untrackPendingUserMessage(optimisticMessageId)
|
|
1715
1743
|
removeOptimisticMessage(optimisticMessageId)
|
|
1716
1744
|
|
|
@@ -1932,7 +1960,6 @@ watch([selectedModel, availableModels], () => {
|
|
|
1932
1960
|
|
|
1933
1961
|
<div class="sticky bottom-0 shrink-0 border-t border-default bg-default/95 px-4 py-3 backdrop-blur md:px-6">
|
|
1934
1962
|
<div class="mx-auto w-full max-w-5xl">
|
|
1935
|
-
<TunnelNotice class="mb-3" />
|
|
1936
1963
|
<UAlert
|
|
1937
1964
|
v-if="composerError"
|
|
1938
1965
|
color="error"
|
|
@@ -6,6 +6,8 @@ import type {
|
|
|
6
6
|
ProjectRecord,
|
|
7
7
|
ProjectResponse,
|
|
8
8
|
ProjectsResponse,
|
|
9
|
+
ServiceUpdateResponse,
|
|
10
|
+
ServiceUpdateStatus,
|
|
9
11
|
StartProjectResult
|
|
10
12
|
} from '~~/shared/codori'
|
|
11
13
|
|
|
@@ -16,8 +18,16 @@ const mergeProject = (projects: ProjectRecord[], nextProject: ProjectRecord) =>
|
|
|
16
18
|
|
|
17
19
|
export const useProjects = () => {
|
|
18
20
|
const projects = useState<ProjectRecord[]>('codori-projects', () => [])
|
|
21
|
+
const serviceUpdate = useState<ServiceUpdateStatus>('codori-service-update', () => ({
|
|
22
|
+
enabled: false,
|
|
23
|
+
updateAvailable: false,
|
|
24
|
+
updating: false,
|
|
25
|
+
installedVersion: null,
|
|
26
|
+
latestVersion: null
|
|
27
|
+
}))
|
|
19
28
|
const loaded = useState<boolean>('codori-projects-loaded', () => false)
|
|
20
29
|
const loading = useState<boolean>('codori-projects-loading', () => false)
|
|
30
|
+
const serviceUpdatePending = useState<boolean>('codori-service-update-pending', () => false)
|
|
21
31
|
const pendingProjectId = useState<string | null>('codori-projects-pending-id', () => null)
|
|
22
32
|
const error = useState<string | null>('codori-projects-error', () => null)
|
|
23
33
|
const configuredBase = String(useRuntimeConfig().public.serverBase ?? '')
|
|
@@ -36,6 +46,14 @@ export const useProjects = () => {
|
|
|
36
46
|
loading.value = true
|
|
37
47
|
error.value = null
|
|
38
48
|
try {
|
|
49
|
+
void $fetch<ServiceUpdateResponse>(toApiUrl('/service/update'))
|
|
50
|
+
.then((response) => {
|
|
51
|
+
serviceUpdate.value = response.serviceUpdate
|
|
52
|
+
})
|
|
53
|
+
.catch(() => {
|
|
54
|
+
// Keep project discovery responsive even if the update check stalls or fails.
|
|
55
|
+
})
|
|
56
|
+
|
|
39
57
|
const response = await $fetch<ProjectsResponse>(toApiUrl('/projects'))
|
|
40
58
|
projects.value = response.projects
|
|
41
59
|
loaded.value = true
|
|
@@ -87,13 +105,37 @@ export const useProjects = () => {
|
|
|
87
105
|
return projects.value.find((project: ProjectRecord) => project.projectId === projectId) ?? null
|
|
88
106
|
}
|
|
89
107
|
|
|
108
|
+
const triggerServiceUpdate = async () => {
|
|
109
|
+
if (serviceUpdatePending.value || serviceUpdate.value.updating) {
|
|
110
|
+
return serviceUpdate.value
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
serviceUpdatePending.value = true
|
|
114
|
+
error.value = null
|
|
115
|
+
try {
|
|
116
|
+
const response = await $fetch<ServiceUpdateResponse>(toApiUrl('/service/update'), {
|
|
117
|
+
method: 'POST'
|
|
118
|
+
})
|
|
119
|
+
serviceUpdate.value = response.serviceUpdate
|
|
120
|
+
return response.serviceUpdate
|
|
121
|
+
} catch (caughtError) {
|
|
122
|
+
error.value = caughtError instanceof Error ? caughtError.message : String(caughtError)
|
|
123
|
+
return serviceUpdate.value
|
|
124
|
+
} finally {
|
|
125
|
+
serviceUpdatePending.value = false
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
90
129
|
return {
|
|
91
130
|
projects,
|
|
131
|
+
serviceUpdate,
|
|
92
132
|
loaded,
|
|
93
133
|
loading,
|
|
134
|
+
serviceUpdatePending,
|
|
94
135
|
error,
|
|
95
136
|
pendingProjectId,
|
|
96
137
|
refreshProjects,
|
|
138
|
+
triggerServiceUpdate,
|
|
97
139
|
startProject,
|
|
98
140
|
stopProject,
|
|
99
141
|
getProject
|
package/app/layouts/default.vue
CHANGED
|
@@ -1,7 +1,27 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { computed, ref } from 'vue'
|
|
3
|
+
import { useProjects } from '../composables/useProjects'
|
|
3
4
|
|
|
4
5
|
const sidebarCollapsed = ref(false)
|
|
6
|
+
const { serviceUpdate, serviceUpdatePending, triggerServiceUpdate } = useProjects()
|
|
7
|
+
|
|
8
|
+
const showServiceUpdateButton = computed(() =>
|
|
9
|
+
serviceUpdate.value.enabled && (serviceUpdate.value.updateAvailable || serviceUpdate.value.updating)
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
const serviceUpdateLabel = computed(() =>
|
|
13
|
+
serviceUpdate.value.updating ? 'Updating' : 'Update'
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
const serviceUpdateTooltip = computed(() => {
|
|
17
|
+
if (!serviceUpdate.value.latestVersion || !serviceUpdate.value.installedVersion) {
|
|
18
|
+
return serviceUpdate.value.updating ? 'Applying the latest server package update.' : 'Install the latest @codori/server package.'
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return serviceUpdate.value.updating
|
|
22
|
+
? `Updating @codori/server ${serviceUpdate.value.installedVersion} -> ${serviceUpdate.value.latestVersion}`
|
|
23
|
+
: `Update @codori/server ${serviceUpdate.value.installedVersion} -> ${serviceUpdate.value.latestVersion}`
|
|
24
|
+
})
|
|
5
25
|
|
|
6
26
|
const sidebarUi = computed(() =>
|
|
7
27
|
sidebarCollapsed.value
|
|
@@ -46,13 +66,44 @@ const sidebarUi = computed(() =>
|
|
|
46
66
|
class="size-5"
|
|
47
67
|
/>
|
|
48
68
|
</div>
|
|
49
|
-
<div
|
|
50
|
-
|
|
51
|
-
|
|
69
|
+
<div
|
|
70
|
+
v-if="!collapsed"
|
|
71
|
+
class="flex min-w-0 flex-1 items-start justify-between gap-3"
|
|
72
|
+
>
|
|
73
|
+
<div class="min-w-0">
|
|
74
|
+
<div class="text-sm font-semibold">
|
|
75
|
+
Codori
|
|
76
|
+
</div>
|
|
77
|
+
<div class="text-xs text-muted">
|
|
78
|
+
Codex project control
|
|
79
|
+
</div>
|
|
52
80
|
</div>
|
|
53
|
-
<
|
|
81
|
+
<UTooltip
|
|
82
|
+
v-if="showServiceUpdateButton"
|
|
83
|
+
:text="serviceUpdateTooltip"
|
|
84
|
+
>
|
|
85
|
+
<UButton
|
|
86
|
+
color="neutral"
|
|
87
|
+
variant="outline"
|
|
88
|
+
size="xs"
|
|
89
|
+
:loading="serviceUpdatePending || serviceUpdate.updating"
|
|
90
|
+
:disabled="serviceUpdatePending || serviceUpdate.updating"
|
|
91
|
+
@click="triggerServiceUpdate"
|
|
92
|
+
>
|
|
93
|
+
{{ serviceUpdateLabel }}
|
|
94
|
+
</UButton>
|
|
95
|
+
</UTooltip>
|
|
96
|
+
</div>
|
|
97
|
+
<div
|
|
98
|
+
v-else-if="collapsed"
|
|
99
|
+
class="sr-only"
|
|
100
|
+
>
|
|
101
|
+
<span>
|
|
102
|
+
Codori
|
|
103
|
+
</span>
|
|
104
|
+
<span>
|
|
54
105
|
Codex project control
|
|
55
|
-
</
|
|
106
|
+
</span>
|
|
56
107
|
</div>
|
|
57
108
|
</div>
|
|
58
109
|
</template>
|
package/app/pages/index.vue
CHANGED
package/package.json
CHANGED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { defineEventHandler } from 'h3'
|
|
2
|
+
import type { ServiceUpdateResponse } from '~~/shared/codori'
|
|
3
|
+
import { proxyServerRequest } from '../../../utils/server-proxy'
|
|
4
|
+
|
|
5
|
+
export default defineEventHandler(async (event) =>
|
|
6
|
+
await proxyServerRequest<ServiceUpdateResponse>(event, '/api/service/update')
|
|
7
|
+
)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { defineEventHandler } from 'h3'
|
|
2
|
+
import type { ServiceUpdateResponse } from '~~/shared/codori'
|
|
3
|
+
import { proxyServerRequest } from '../../../utils/server-proxy'
|
|
4
|
+
|
|
5
|
+
export default defineEventHandler(async (event) =>
|
|
6
|
+
await proxyServerRequest<ServiceUpdateResponse>(event, '/api/service/update', {
|
|
7
|
+
method: 'POST'
|
|
8
|
+
})
|
|
9
|
+
)
|
package/shared/codex-chat.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { CodexThread, CodexThreadItem, CodexUserInput } from './codex-rpc'
|
|
|
2
2
|
|
|
3
3
|
export const EVENT_PART = 'data-thread-event' as const
|
|
4
4
|
export const ITEM_PART = 'data-thread-item' as const
|
|
5
|
+
export const THINKING_PLACEHOLDER_MESSAGE_ID = 'assistant-thinking-placeholder'
|
|
5
6
|
|
|
6
7
|
export type ThreadEventData =
|
|
7
8
|
| {
|
|
@@ -336,11 +337,13 @@ const normalizeParts = (message: ChatMessage): ChatPart[] =>
|
|
|
336
337
|
return part
|
|
337
338
|
})
|
|
338
339
|
|
|
340
|
+
const normalizeMessage = (message: ChatMessage): ChatMessage => ({
|
|
341
|
+
...message,
|
|
342
|
+
parts: normalizeParts(message)
|
|
343
|
+
})
|
|
344
|
+
|
|
339
345
|
export const upsertStreamingMessage = (messages: ChatMessage[], nextMessage: ChatMessage) => {
|
|
340
|
-
const normalizedMessage =
|
|
341
|
-
...nextMessage,
|
|
342
|
-
parts: normalizeParts(nextMessage)
|
|
343
|
-
}
|
|
346
|
+
const normalizedMessage = normalizeMessage(nextMessage)
|
|
344
347
|
const nextMessages = messages.slice()
|
|
345
348
|
const existingIndex = nextMessages.findIndex(message => message.id === normalizedMessage.id)
|
|
346
349
|
|
|
@@ -360,3 +363,43 @@ export const upsertStreamingMessage = (messages: ChatMessage[], nextMessage: Cha
|
|
|
360
363
|
|
|
361
364
|
return nextMessages
|
|
362
365
|
}
|
|
366
|
+
|
|
367
|
+
export const replaceStreamingMessage = (messages: ChatMessage[], nextMessage: ChatMessage) => {
|
|
368
|
+
const normalizedMessage = normalizeMessage(nextMessage)
|
|
369
|
+
const nextMessages = messages.slice()
|
|
370
|
+
const existingIndex = nextMessages.findIndex(message => message.id === normalizedMessage.id)
|
|
371
|
+
|
|
372
|
+
if (existingIndex === -1) {
|
|
373
|
+
nextMessages.push(normalizedMessage)
|
|
374
|
+
return nextMessages
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
nextMessages.splice(existingIndex, 1, normalizedMessage)
|
|
378
|
+
return nextMessages
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
export const buildThinkingPlaceholderMessage = (): ChatMessage => ({
|
|
382
|
+
id: THINKING_PLACEHOLDER_MESSAGE_ID,
|
|
383
|
+
role: 'assistant',
|
|
384
|
+
pending: true,
|
|
385
|
+
parts: [{
|
|
386
|
+
type: 'reasoning',
|
|
387
|
+
summary: ['Thinking...'],
|
|
388
|
+
content: [],
|
|
389
|
+
state: 'streaming'
|
|
390
|
+
}]
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
export const showThinkingPlaceholder = (messages: ChatMessage[]) =>
|
|
394
|
+
upsertStreamingMessage(messages, buildThinkingPlaceholderMessage())
|
|
395
|
+
|
|
396
|
+
export const hideThinkingPlaceholder = (messages: ChatMessage[]) => {
|
|
397
|
+
const index = messages.findIndex(message => message.id === THINKING_PLACEHOLDER_MESSAGE_ID)
|
|
398
|
+
if (index === -1) {
|
|
399
|
+
return messages
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const nextMessages = messages.slice()
|
|
403
|
+
nextMessages.splice(index, 1)
|
|
404
|
+
return nextMessages
|
|
405
|
+
}
|
package/shared/codori.ts
CHANGED
|
@@ -14,6 +14,14 @@ export type StartProjectResult = ProjectRecord & {
|
|
|
14
14
|
reusedExisting: boolean
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
export type ServiceUpdateStatus = {
|
|
18
|
+
enabled: boolean
|
|
19
|
+
updateAvailable: boolean
|
|
20
|
+
updating: boolean
|
|
21
|
+
installedVersion: string | null
|
|
22
|
+
latestVersion: string | null
|
|
23
|
+
}
|
|
24
|
+
|
|
17
25
|
export type ProjectsResponse = {
|
|
18
26
|
projects: ProjectRecord[]
|
|
19
27
|
}
|
|
@@ -22,6 +30,10 @@ export type ProjectResponse = {
|
|
|
22
30
|
project: ProjectRecord | StartProjectResult
|
|
23
31
|
}
|
|
24
32
|
|
|
33
|
+
export type ServiceUpdateResponse = {
|
|
34
|
+
serviceUpdate: ServiceUpdateStatus
|
|
35
|
+
}
|
|
36
|
+
|
|
25
37
|
export const normalizeProjectIdParam = (value: string | string[] | undefined) => {
|
|
26
38
|
if (!value) {
|
|
27
39
|
return null
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import { computed } from 'vue'
|
|
3
|
-
|
|
4
|
-
const localHostnames = new Set([
|
|
5
|
-
'localhost',
|
|
6
|
-
'127.0.0.1',
|
|
7
|
-
'::1'
|
|
8
|
-
])
|
|
9
|
-
|
|
10
|
-
const hostname = typeof window === 'undefined' ? 'localhost' : window.location.hostname
|
|
11
|
-
|
|
12
|
-
const shouldShow = computed(() => !localHostnames.has(hostname))
|
|
13
|
-
</script>
|
|
14
|
-
|
|
15
|
-
<template>
|
|
16
|
-
<UAlert
|
|
17
|
-
v-if="shouldShow"
|
|
18
|
-
color="warning"
|
|
19
|
-
variant="soft"
|
|
20
|
-
icon="i-lucide-shield-alert"
|
|
21
|
-
title="Private tunnel is not included"
|
|
22
|
-
>
|
|
23
|
-
<template #description>
|
|
24
|
-
Codori does not create a private tunnel for you. Expose this service through your own network layer such as Tailscale or Cloudflare Tunnel.
|
|
25
|
-
</template>
|
|
26
|
-
</UAlert>
|
|
27
|
-
</template>
|